/*****************************************************************************

gifbuild - dump GIF data in a textual format, or undump it to a GIF

SPDX-License-Identifier: MIT

*****************************************************************************/

#include <stdlib.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <stdbool.h>

#include "gif_lib.h"
#include "getarg.h"

#define PROGRAM_NAME	"gifbuild"

static char
    *VersionStr =
	PROGRAM_NAME
	VERSION_COOKIE
	"	Eric Raymond,	"
	__DATE__ ",   " __TIME__ "\n"
	"(C) Copyright 1992 Eric Raymond.\n";
static char
    *CtrlStr =
	PROGRAM_NAME
	" v%- d%- t%-Characters!s h%- GifFile(s)!*s";

static char KeyLetters[] = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!\"#$%&'()*+,-./:<=>?@[\\]^_`{|}~";
#define PRINTABLES	(sizeof(KeyLetters) - 1)

static void Icon2Gif(char *FileName, FILE *txtin, int fdout);
static void Gif2Icon(char *FileName,
		     int fdin, int fdout,
		     char NameTable[]);
static int EscapeString(char *cp, char *tp);

/******************************************************************************
 Main sequence
******************************************************************************/
int main(int argc, char **argv)
{
    int	NumFiles;
    bool Error,	DisasmFlag = false, HelpFlag = false, TextLineFlag = false;
    char **FileNames = NULL;
    char *TextLines[1];

    if ((Error = GAGetArgs(argc, argv, CtrlStr,
		&GifNoisyPrint, &DisasmFlag, &TextLineFlag, &TextLines[0],
		&HelpFlag, &NumFiles, &FileNames)) != false) {
	GAPrintErrMsg(Error);
	GAPrintHowTo(CtrlStr);
	exit(EXIT_FAILURE);
    }

    if (HelpFlag) {
	(void)fprintf(stderr, VersionStr, GIFLIB_MAJOR, GIFLIB_MINOR);
	GAPrintHowTo(CtrlStr);
	exit(EXIT_SUCCESS);
    }

    if (!DisasmFlag && NumFiles > 1) {
	GIF_MESSAGE("Error in command line parsing - one  text input please.");
	GAPrintHowTo(CtrlStr);
	exit(EXIT_FAILURE);
    }

    if (!DisasmFlag && TextLineFlag) {
	GIF_MESSAGE("Error in command line parsing - -t invalid without -d.");
	GAPrintHowTo(CtrlStr);
	exit(EXIT_FAILURE);
    }


    if (NumFiles == 0)
    {
	if (DisasmFlag)
	    Gif2Icon("Stdin", 0, 1, TextLineFlag ? TextLines[0] : KeyLetters);
	else
	    Icon2Gif("Stdin", stdin, 1);
    }
    else 
    {
	int i;
	for (i = 0; i < NumFiles; i++)
	{
	    FILE	*fp;

	    if ((fp = fopen(FileNames[i], "r")) == (FILE *)NULL)
	    {
		(void) fprintf(stderr, "Can't open %s\n", FileNames[i]);
		exit(EXIT_FAILURE);
	    }

	    if (DisasmFlag)
	    {
		printf("#\n# GIF information from %s\n", FileNames[i]);
		Gif2Icon(FileNames[i], -1, 1, TextLineFlag ? TextLines[0] : KeyLetters);
	    }
	    else
	    {
		Icon2Gif(FileNames[i], fp, 1);
	    }

	    (void) fclose(fp);
	}
    }

    return 0;
}

/******************************************************************************
 Parse image directives
******************************************************************************/
#define PARSE_ERROR(str)  (void) fprintf(stderr,"%s:%d: %s\n",FileName,LineNum,str);

static void Icon2Gif(char *FileName, FILE *txtin, int fdout)
{
    unsigned int	ColorMapSize = 0;
    GifColorType GlobalColorMap[256], LocalColorMap[256],
	*ColorMap = GlobalColorMap;
    char GlobalColorKeys[PRINTABLES], LocalColorKeys[PRINTABLES],
	*KeyTable = GlobalColorKeys;
    bool SortFlag = false;
    unsigned int ExtCode, intval;
    int red, green, blue, n;
    char buf[BUFSIZ * 2], InclusionFile[64];
    GifFileType *GifFileOut;
    SavedImage *NewImage = NULL;
    int LeadingExtensionBlockCount = 0;
    ExtensionBlock *LeadingExtensionBlocks = NULL;
    int ErrorCode, LineNum = 0;

    if ((GifFileOut = EGifOpenFileHandle(fdout, &ErrorCode)) == NULL) {
	PrintGifError(ErrorCode);
	exit(EXIT_FAILURE);
    }

    /* OK, interpret directives */
    /* coverity[tainted_data_transitive] */
    while (fgets(buf, sizeof(buf), txtin) != (char *)NULL)
    {
	char	*cp;

	++LineNum;

	/*
	 * Skip lines consisting only of whitespace and comments
	 */
	for (cp = buf; isspace((int)(*cp)); cp++)
	    continue;
	if (*cp == '#' || *cp == '\0')
	    continue;

	/*
	 * If there's a trailing comment, nuke it and all preceding whitespace.
	 * But preserve the EOL.
	 */
	if ((cp = strchr(buf, '#')) && (cp == strrchr(cp, '#')))
	{
	    while (isspace((int)(*--cp)))
		continue;
	    *++cp = '\n';
	    *++cp = '\0';
	}

	/*
	 * Explicit header declarations
	 */

	if (sscanf(buf, "screen width %d\n", &GifFileOut->SWidth) == 1)
	    continue;

	else if (sscanf(buf, "screen height %d\n", &GifFileOut->SHeight) == 1)
	    continue;

	else if (sscanf(buf, "screen colors %d\n", &n) == 1)
	{
	    int	ResBits = GifBitSize(n);

	    if (n > 256 || n < 0 || n != (1 << ResBits))
	    {
		PARSE_ERROR("Invalid color resolution value.");
		exit(EXIT_FAILURE);
	    }

	    GifFileOut->SColorResolution = ResBits;
	    continue;
	}

	else if (sscanf(buf,
			"screen background %d\n",
			&GifFileOut->SBackGroundColor) == 1)
	    continue;

	else if (sscanf(buf, "pixel aspect byte %u\n", &intval) == 1) {
	    GifFileOut->AspectByte = (GifByteType)(intval & 0xff);
	    continue;
	}

	/*
	 * Color table parsing
	 */

	else if (strcmp(buf, "screen map\n") == 0)
	{
	    if (GifFileOut->SColorMap != NULL)
	    {
		PARSE_ERROR("You've already declared a global color map.");
		exit(EXIT_FAILURE);
	    }

	    ColorMapSize = 0;
	    ColorMap = GlobalColorMap;
	    SortFlag = false;
	    KeyTable = GlobalColorKeys;
	    memset(GlobalColorKeys, '\0', sizeof(GlobalColorKeys));
	}

	else if (strcmp(buf, "image map\n") == 0)
	{
	    if (NewImage == NULL)
	    {
		PARSE_ERROR("No previous image declaration.");
		exit(EXIT_FAILURE);
	    }

	    ColorMapSize = 0;
	    ColorMap = LocalColorMap;
	    KeyTable = LocalColorKeys;
	    memset(LocalColorKeys, '\0', sizeof(LocalColorKeys));
	}

	else if (sscanf(buf, "	rgb %d %d %d is %c",
		   &red, &green, &blue, &KeyTable[ColorMapSize]) == 4)
	{
	    ColorMap[ColorMapSize].Red = red;
	    ColorMap[ColorMapSize].Green = green;
	    ColorMap[ColorMapSize].Blue = blue;
	    ColorMapSize++;
	}

	else if (sscanf(buf, "	rgb %d %d %d", &red, &green, &blue) == 3)
	{
	    ColorMap[ColorMapSize].Red = red;
	    ColorMap[ColorMapSize].Green = green;
	    ColorMap[ColorMapSize].Blue = blue;
	    ColorMapSize++;
	}

	else if (strcmp(buf, "	sort flag on\n") == 0)
	    SortFlag = true;

	else if (strcmp(buf, "	sort flag off\n") == 0)
	    SortFlag = false;

	else if (strcmp(buf, "end\n") == 0)
	{
	    ColorMapObject	*NewMap;

	    NewMap = GifMakeMapObject(1 << GifBitSize(ColorMapSize), ColorMap);
	    if (NewMap == (ColorMapObject *)NULL)
	    {
		PARSE_ERROR("Out of memory while allocating new color map.");
		exit(EXIT_FAILURE);
	    }

	    NewMap->SortFlag = SortFlag;

	    if (NewImage)
		NewImage->ImageDesc.ColorMap = NewMap;
	    else
		GifFileOut->SColorMap = NewMap;
	}

	/* GIF inclusion */
	/* ugly magic number is because scanf has no */
	else if (sscanf(buf, "include %63s", InclusionFile) == 1)
	{
	    int		ErrorCode;
	    bool	DoTranslation;
	    GifPixelType	Translation[256];

	    GifFileType	*Inclusion;
	    SavedImage	*CopyFrom;

	    if ((Inclusion = DGifOpenFileName(InclusionFile, &ErrorCode)) == NULL) {
		PrintGifError(ErrorCode);
		exit(EXIT_FAILURE);
	    }

	    if (DGifSlurp(Inclusion) == GIF_ERROR)
	    {
		PARSE_ERROR("Inclusion read failed.");
		if (Inclusion != NULL) {
		    PrintGifError(Inclusion->Error);
		    DGifCloseFile(Inclusion, NULL);
		}
		if (GifFileOut != NULL) {
		    EGifCloseFile(GifFileOut, NULL);
		};
		exit(EXIT_FAILURE);
	    }

	    //cppcheck-suppress nullPointerRedundantCheck
	    if ((DoTranslation = (GifFileOut->SColorMap!=(ColorMapObject*)NULL)))
	    {
		ColorMapObject	*UnionMap;

		//cppcheck-suppress nullPointerRedundantCheck
		UnionMap = GifUnionColorMap(GifFileOut->SColorMap, Inclusion->SColorMap, Translation);

		if (UnionMap == NULL)
		{
		    PARSE_ERROR("Inclusion failed --- global map conflict.");
		    //cppcheck-suppress nullPointerRedundantCheck
		    PrintGifError(GifFileOut->Error);
		    if (Inclusion != NULL) DGifCloseFile(Inclusion, NULL);
		    if (GifFileOut != NULL) EGifCloseFile(GifFileOut, NULL);
		    exit(EXIT_FAILURE);
		}

		GifFreeMapObject(GifFileOut->SColorMap);
		GifFileOut->SColorMap = UnionMap;
	    }

	    //cppcheck-suppress nullPointerRedundantCheck
	    for (CopyFrom = Inclusion->SavedImages;
		 //cppcheck-suppress nullPointerRedundantCheck
		 CopyFrom < Inclusion->SavedImages + Inclusion->ImageCount;
		 CopyFrom++)
	    {
		SavedImage	*NewImage;
		if ((NewImage = GifMakeSavedImage(GifFileOut, CopyFrom)) == NULL)
		{
		    PARSE_ERROR("Inclusion failed --- out of memory.");
		    //cppcheck-suppress nullPointerRedundantCheck
		    PrintGifError(GifFileOut->Error);
		    if (Inclusion != NULL) DGifCloseFile(Inclusion, NULL);
		    if (GifFileOut != NULL) EGifCloseFile(GifFileOut, NULL);
		    exit(EXIT_FAILURE);
		}
		else if (DoTranslation)
		    GifApplyTranslation(NewImage, Translation);

		GifQprintf(
		        "%s: Image %d at (%d, %d) [%dx%d]: from %s\n",
			PROGRAM_NAME, GifFileOut->ImageCount,
			NewImage->ImageDesc.Left, NewImage->ImageDesc.Top,
			NewImage->ImageDesc.Width, NewImage->ImageDesc.Height,
			InclusionFile);
	    }

	    (void) DGifCloseFile(Inclusion, NULL);
	}

	/*
	 * Extension blocks.
	 */
	else if (strcmp(buf, "comment\n") == 0)
	{
	    int bc = 0;
	    while (fgets(buf, sizeof(buf), txtin) != (char *)NULL)
		if (strcmp(buf, "end\n") == 0)
		    break;
	        else
		{
		    int Len;

		    buf[strlen(buf) - 1] = '\0';
		    Len = EscapeString(buf, buf);
		    if (GifAddExtensionBlock(&LeadingExtensionBlockCount,
					     &LeadingExtensionBlocks,
					     bc++ == CONTINUE_EXT_FUNC_CODE ? COMMENT_EXT_FUNC_CODE : 0,
					     Len,
					     (unsigned char *)buf) == GIF_ERROR) {
			PARSE_ERROR("out of memory while adding comment block.");
			exit(EXIT_FAILURE);
		    }
		}
	}
	else if (strcmp(buf, "plaintext\n") == 0)
	{
	    int bc = 0;
	    while (fgets(buf, sizeof(buf), txtin) != (char *)NULL)
		if (strcmp(buf, "end\n") == 0)
		    break;
	        else
		{
		    int Len;

		    buf[strlen(buf) - 1] = '\0';
		    Len = EscapeString(buf, buf);
		    if (GifAddExtensionBlock(&LeadingExtensionBlockCount,
					     &LeadingExtensionBlocks,
					     bc++ == CONTINUE_EXT_FUNC_CODE ? PLAINTEXT_EXT_FUNC_CODE : 0,
					     Len, 
					     (unsigned char *)buf) == GIF_ERROR) {
			PARSE_ERROR("out of memory while adding plaintext block.");
			exit(EXIT_FAILURE);
		    }
		}
	}
	else if (strcmp(buf, "graphics control\n") == 0)
	{
	    GraphicsControlBlock gcb;
	    size_t Len;

	    memset(&gcb, '\0', sizeof(gcb));
	    gcb.TransparentColor = NO_TRANSPARENT_COLOR;
	    while (fgets(buf, sizeof(buf), txtin) != (char *)NULL)
		if (strcmp(buf, "end\n") == 0)
		    break;
	        else
		{
		    char *tp = buf;

		    while (isspace(*tp))
			tp++;
		    if (sscanf(tp, "disposal mode %d\n", &gcb.DisposalMode))
			continue;
		    if (strcmp(tp, "user input flag on\n") == 0) {
			gcb.UserInputFlag = true;
			continue;
		    }
		    if (strcmp(tp, "user input flag off\n") == 0) {
			gcb.UserInputFlag = false;
			continue;
		    }
		    if (sscanf(tp, "delay %d\n", &gcb.DelayTime))
			continue;
		    if (sscanf(tp, "transparent index %d\n",
			       &gcb.TransparentColor))
			continue;
		    (void) fputs(tp, stderr);
		    PARSE_ERROR("unrecognized directive in GCB block.");
		    exit(EXIT_FAILURE);
		}
	    Len = EGifGCBToExtension(&gcb, (GifByteType *)buf);
	    if (GifAddExtensionBlock(&LeadingExtensionBlockCount,
				     &LeadingExtensionBlocks,
				     GRAPHICS_EXT_FUNC_CODE,
				     Len,
				     (unsigned char *)buf) == GIF_ERROR) {
		PARSE_ERROR("out of memory while adding GCB.");
		exit(EXIT_FAILURE);
	    }

	}
	else if (sscanf(buf, "netscape loop %u", &intval))
	{
	    unsigned char params[3] = {1, 0, 0};
	    /* Create a Netscape 2.0 loop block */
	    if (GifAddExtensionBlock(&LeadingExtensionBlockCount,
				     &LeadingExtensionBlocks,
				     APPLICATION_EXT_FUNC_CODE,
				     11,
				     (unsigned char *)"NETSCAPE2.0")==GIF_ERROR) {
		PARSE_ERROR("out of memory while adding loop block.");
		exit(EXIT_FAILURE);
	    }
	    params[1] = (intval & 0xff);
	    params[2] = (intval >> 8) & 0xff;
	    if (GifAddExtensionBlock(&LeadingExtensionBlockCount,
				     &LeadingExtensionBlocks,
				     0, sizeof(params), params) == GIF_ERROR) {
		PARSE_ERROR("out of memory while adding loop continuation.");
		exit(EXIT_FAILURE);
	    }
	    
	}
	else if (sscanf(buf, "extension %x", &ExtCode))
	{
	    int bc = 0;
	    while (fgets(buf, sizeof(buf), txtin) != (char *)NULL)
		if (strcmp(buf, "end\n") == 0)
		    break;
	        else
		{
		    int Len;

		    buf[strlen(buf) - 1] = '\0';
		    Len = EscapeString(buf, buf);
		    if (GifAddExtensionBlock(&LeadingExtensionBlockCount,
					     &LeadingExtensionBlocks,
					     bc++ == CONTINUE_EXT_FUNC_CODE ? ExtCode : 0, 
					     Len,
					     (unsigned char *)buf) == GIF_ERROR) {
			PARSE_ERROR("out of memory while adding extension block.");
			exit(EXIT_FAILURE);
		    }
		}
	}

	/*
	 * Explicit image declarations 
	 */

	else if (strcmp(buf, "image\n") == 0)
	{
	    if ((NewImage = GifMakeSavedImage(GifFileOut, NULL)) == (SavedImage *)NULL)
	    {
		PARSE_ERROR("Out of memory while allocating image block.");
		exit(EXIT_FAILURE);
	    }

	    /* use global table unless user specifies a local one */
	    ColorMap = GlobalColorMap;
	    KeyTable = GlobalColorKeys;

	    /* connect leading extension blocks */
	    NewImage->ExtensionBlockCount = LeadingExtensionBlockCount;
	    NewImage->ExtensionBlocks = LeadingExtensionBlocks;
	    LeadingExtensionBlockCount = 0;
	    LeadingExtensionBlocks = NULL;
	}

	/*
	 * Nothing past this point is valid unless we've seen a previous
	 * image declaration.
	 */
	else if (NewImage == (SavedImage *)NULL)
	{
	    (void) fputs(buf, stderr);
	    PARSE_ERROR("Syntax error in header block.");
	    exit(EXIT_FAILURE);
	}

	/*
	 * Accept image attributes
	 */
	else if (sscanf(buf, "image top %d\n", &NewImage->ImageDesc.Top) == 1)
	    continue;

	else if (sscanf(buf, "image left %d\n", &NewImage->ImageDesc.Left)== 1)
	    continue;

	else if (strcmp(buf, "image interlaced\n") == 0)
	{
	    NewImage->ImageDesc.Interlace = true;
	    continue;
	}

	else if (sscanf(buf,
			"image bits %d by %d",
			&NewImage->ImageDesc.Width,
			&NewImage->ImageDesc.Height) == 2)
	{
	    int i, j;
	    static GifPixelType *Raster, *cp;
	    int c;
	    bool hex = (strstr(buf, "hex") != NULL);

	    /* coverity[overflow_sink] */
	    if ((Raster = (GifPixelType *) malloc(sizeof(GifPixelType) * NewImage->ImageDesc.Width * NewImage->ImageDesc.Height))
		== NULL) {
		PARSE_ERROR("Failed to allocate raster block, aborted.");
		exit(EXIT_FAILURE);
	    }

	    GifQprintf("%s: Image %d at (%d, %d) [%dx%d]:     ",
		       PROGRAM_NAME, GifFileOut->ImageCount,
		       NewImage->ImageDesc.Left, NewImage->ImageDesc.Top,
		       NewImage->ImageDesc.Width, NewImage->ImageDesc.Height);

	    cp = Raster;
	    for (i = 0; i < NewImage->ImageDesc.Height; i++) {

		char	*dp;

		for (j = 0; j < NewImage->ImageDesc.Width; j++)
		    if ((c = fgetc(txtin)) == EOF) {
			PARSE_ERROR("input file ended prematurely.");
			exit(EXIT_FAILURE);
		    }
		    else if (c == '\n')
		    {
			--j;
			++LineNum;
		    }
		    else if (isspace(c))
			--j;
		    else if (hex) 
		    {
			const static char *hexdigits = "0123456789ABCDEF";
			unsigned char hi, lo;
			dp = strchr(hexdigits, toupper(c));
			if (dp == NULL) {
			    PARSE_ERROR("Invalid hex high byte.");
			    exit(EXIT_FAILURE);
			}
			hi = (dp - hexdigits);
			if ((c = fgetc(txtin)) == EOF) {
			    PARSE_ERROR("input file ended prematurely.");
			    exit(EXIT_FAILURE);
			}
			dp = strchr(hexdigits, toupper(c));
			if (dp == NULL) {
			    PARSE_ERROR("Invalid hex low byte.");
			    exit(EXIT_FAILURE);
			}
			lo = (dp - hexdigits);
			*cp++ = (hi << 4) | lo;
		    }
		    else if ((dp = strchr(KeyTable, c)))
			*cp++ = (dp - KeyTable);
		    else {
			PARSE_ERROR("Invalid ASCII pixel key.");
			exit(EXIT_FAILURE);
		    }

		if (GifNoisyPrint)
		    fprintf(stderr, "\b\b\b\b%-4d", i);
	    }

	    if (GifNoisyPrint)
		putc('\n', stderr);

	    NewImage->RasterBits = (unsigned char *) Raster;
	}
	else
	{
	    (void) fputs(buf, stderr);
	    PARSE_ERROR("Syntax error in image description.");
	    exit(EXIT_FAILURE);
	}
    }

    /* connect trailing extension blocks */
    GifFileOut->ExtensionBlockCount = LeadingExtensionBlockCount;
    GifFileOut->ExtensionBlocks = LeadingExtensionBlocks;
    //LeadingExtensionBlockCount = 0;
    LeadingExtensionBlocks = NULL;
 
    EGifSpew(GifFileOut);
}

static void VisibleDumpBuffer(GifByteType *buf, int len)
/* Visibilize a given string */
{
    GifByteType	*cp;

    for (cp = buf; cp < buf + len; cp++)
    {
	if (isprint((int)(*cp)) || *cp == ' ')
	    putchar(*cp);
	else if (*cp == '\n')
	{
	    putchar('\\'); putchar('n');
	}
	else if (*cp == '\r')
	{
	    putchar('\\'); putchar('r');
	}
	else if (*cp == '\b')
	{
	    putchar('\\'); putchar('b');
	}
	else if (*cp < ' ')
	{
	    putchar('\\'); putchar('^'); putchar('@' + *cp);
	}
	else
	    printf("\\0x%02x", *cp);
    }
}

static void DumpExtensions(GifFileType *GifFileOut, 
			   int ExtensionBlockCount,
			   ExtensionBlock *ExtensionBlocks)
{
    ExtensionBlock *ep;

    for (ep = ExtensionBlocks; 
	 ep < ExtensionBlocks + ExtensionBlockCount;
	 ep++) {
	bool last = (ep - ExtensionBlocks == (ExtensionBlockCount - 1));
	if (ep->Function == COMMENT_EXT_FUNC_CODE) {
	    printf("comment\n");
	    VisibleDumpBuffer(ep->Bytes, ep->ByteCount);
	    putchar('\n');
	    while (!last && ep[1].Function == CONTINUE_EXT_FUNC_CODE) {
		++ep;
		last = (ep - ExtensionBlocks == (ExtensionBlockCount - 1));
		VisibleDumpBuffer(ep->Bytes, ep->ByteCount);
		putchar('\n');
	    }
	    printf("end\n\n");
	}
	else if (ep->Function == PLAINTEXT_EXT_FUNC_CODE) {
	    printf("plaintext\n");
	    VisibleDumpBuffer(ep->Bytes, ep->ByteCount);
	    putchar('\n');
	    while (!last && ep[1].Function == CONTINUE_EXT_FUNC_CODE) {
		++ep;
		last = (ep - ExtensionBlocks == (ExtensionBlockCount - 1));
		VisibleDumpBuffer(ep->Bytes, ep->ByteCount);
		putchar('\n');
	    }
	    printf("end\n\n");
	}
	else if (ep->Function == GRAPHICS_EXT_FUNC_CODE)
	{
	    GraphicsControlBlock gcb;
	    printf("graphics control\n");
	    if (DGifExtensionToGCB(ep->ByteCount, ep->Bytes, &gcb) == GIF_ERROR) {
		GIF_MESSAGE("invalid graphics control block");
		exit(EXIT_FAILURE);
	    }
	    printf("\tdisposal mode %d\n", gcb.DisposalMode);
	    printf("\tuser input flag %s\n", 
		   gcb.UserInputFlag ? "on" : "off");
	    printf("\tdelay %d\n", gcb.DelayTime);
	    printf("\ttransparent index %d\n", gcb.TransparentColor);
	    printf("end\n\n");
	}
	else if (!last
		 && ep->Function == APPLICATION_EXT_FUNC_CODE
		 && ep->ByteCount >= 11
		 && (ep+1)->ByteCount >= 3
		 && memcmp(ep->Bytes, "NETSCAPE2.0", 11) == 0) {
	    unsigned char *params = (++ep)->Bytes;
	    unsigned int loopcount = params[1] | (params[2] << 8);
	    printf("netscape loop %u\n\n", loopcount);
	}
	else {
	    printf("extension 0x%02x\n", ep->Function);
	    VisibleDumpBuffer(ep->Bytes, ep->ByteCount);
	    while (!last && ep[1].Function == CONTINUE_EXT_FUNC_CODE) {
		++ep;
		last = (ep - ExtensionBlocks == (ExtensionBlockCount - 1));
		VisibleDumpBuffer(ep->Bytes, ep->ByteCount);
		putchar('\n');
	    }
	    printf("end\n\n");
	}
    }
}

static void Gif2Icon(char *FileName,
		     int fdin, int fdout,
		     char NameTable[])
{
    int ErrorCode, im, i, j, ColorCount = 0;
    GifFileType *GifFile;

    if (fdin == -1) {
	if ((GifFile = DGifOpenFileName(FileName, &ErrorCode)) == NULL) {
	    PrintGifError(ErrorCode);
	    exit(EXIT_FAILURE);
	}
    }
    else {
	/* Use stdin instead: */
	if ((GifFile = DGifOpenFileHandle(fdin, &ErrorCode)) == NULL) {
	    PrintGifError(ErrorCode);
	    exit(EXIT_FAILURE);
	}
    }

    if (DGifSlurp(GifFile) == GIF_ERROR) {
	PrintGifError(GifFile->Error);
	exit(EXIT_FAILURE);
    }

    printf("screen width %d\nscreen height %d\n",
	   GifFile->SWidth, GifFile->SHeight);

    printf("screen colors %d\nscreen background %d\npixel aspect byte %u\n\n",
	   1 << GifFile->SColorResolution,
	   GifFile->SBackGroundColor,
	   (unsigned)GifFile->AspectByte);

    if (GifFile->SColorMap)
    {
	printf("screen map\n");

	printf("\tsort flag %s\n", GifFile->SColorMap->SortFlag ? "on" : "off");

	for (i = 0; i < GifFile->SColorMap->ColorCount; i++)
	    if (GifFile->SColorMap->ColorCount < PRINTABLES)
		printf("\trgb %03d %03d %03d is %c\n",
		       GifFile->SColorMap ->Colors[i].Red,
		       GifFile->SColorMap ->Colors[i].Green,
		       GifFile->SColorMap ->Colors[i].Blue,
		       NameTable[i]);
	    else
		printf("\trgb %03d %03d %03d\n",
		       GifFile->SColorMap ->Colors[i].Red,
		       GifFile->SColorMap ->Colors[i].Green,
		       GifFile->SColorMap ->Colors[i].Blue);
	printf("end\n\n");
    }

    for (im = 0; im < GifFile->ImageCount; im++) {
	SavedImage *image = &GifFile->SavedImages[im];

	DumpExtensions(GifFile, 
		       image->ExtensionBlockCount, image->ExtensionBlocks);

	printf("image # %d\nimage left %d\nimage top %d\n",
	       im+1, image->ImageDesc.Left, image->ImageDesc.Top);
	if (image->ImageDesc.Interlace)
	    printf("image interlaced\n");

	if (image->ImageDesc.ColorMap)
	{
	    printf("image map\n");

	    printf("\tsort flag %s\n", 
		   image->ImageDesc.ColorMap->SortFlag ? "on" : "off");

	    if (image->ImageDesc.ColorMap->ColorCount < PRINTABLES)
		for (i = 0; i < image->ImageDesc.ColorMap->ColorCount; i++)
		    printf("\trgb %03d %03d %03d is %c\n",
			   image->ImageDesc.ColorMap ->Colors[i].Red,
			   image->ImageDesc.ColorMap ->Colors[i].Green,
			   image->ImageDesc.ColorMap ->Colors[i].Blue,
			   NameTable[i]);
	    else
		for (i = 0; i < image->ImageDesc.ColorMap->ColorCount; i++)
		    printf("\trgb %03d %03d %03d\n",
			   image->ImageDesc.ColorMap ->Colors[i].Red,
			   image->ImageDesc.ColorMap ->Colors[i].Green,
			   image->ImageDesc.ColorMap ->Colors[i].Blue);
	    printf("end\n\n");
	}

	/* one of these conditions has to be true */
	if (image->ImageDesc.ColorMap)
	    ColorCount = image->ImageDesc.ColorMap->ColorCount;
	else if (GifFile->SColorMap)
	    ColorCount = GifFile->SColorMap->ColorCount;

	if (ColorCount < PRINTABLES)
	    printf("image bits %d by %d\n",
		   image->ImageDesc.Width, image->ImageDesc.Height);
	else
	    printf("image bits %d by %d hex\n",
		   image->ImageDesc.Width, image->ImageDesc.Height);
	for (i = 0; i < image->ImageDesc.Height; i++) {
	    for (j = 0; j < image->ImageDesc.Width; j++) {
		GifByteType ch = image->RasterBits[i*image->ImageDesc.Width + j];
		if (ColorCount < PRINTABLES && ch < PRINTABLES)
		    putchar(NameTable[ch]);
		else
		    printf("%02x", ch);
	    }
	    putchar('\n');
	}
	putchar('\n');
    }

    DumpExtensions(GifFile, 
		   GifFile->ExtensionBlockCount, GifFile->ExtensionBlocks);

    /* Tell EMACS this is a picture... */
    printf("# The following sets edit modes for GNU EMACS\n");
    printf("# Local ");		/* ...break this up, so that EMACS doesn't */
    printf("Variables:\n");	/* get confused when visiting *this* file! */
    printf("# mode:picture\n");
    printf("# truncate-lines:t\n");
    printf("# End:\n");

    if (fdin == -1)
	(void) printf("# End of %s dump\n", FileName);


    /*
     * Sanity checks.
     */

    /* check that the background color isn't garbage (SF bug #87) */
    if (GifFile->SBackGroundColor < 0
	|| (GifFile->SColorMap && GifFile->SBackGroundColor >= GifFile->SColorMap->ColorCount)) {
        fprintf(stderr, "gifbuild: background color invalid for screen colormap.\n");
    }

    if (DGifCloseFile(GifFile, &ErrorCode) == GIF_ERROR) {
	PrintGifError(ErrorCode);
	exit(EXIT_FAILURE);
    }
}

static int EscapeString(char *cp, char *tp)
/* process standard C-style escape sequences in a string */
{
    char *StartAddr = tp;

    while (*cp)
    {
	int	cval = 0;

	if (*cp == '\\' && strchr("0123456789xX", cp[1]))
	{
	    int dcount = 0;

	    if (*++cp == 'x' || *cp == 'X') {
		char *dp, *hex = "00112233445566778899aAbBcCdDeEfF";
		for (++cp; (dp = strchr(hex, *cp)) && (dcount++ < 2); cp++)
		    cval = (cval * 16) + (dp - hex) / 2;
	    } else if (*cp == '0')
		while (strchr("01234567",*cp) != (char*)NULL && (dcount++ < 3))
		    cval = (cval * 8) + (*cp++ - '0');
	    else
		while ((strchr("0123456789",*cp)!=(char*)NULL)&&(dcount++ < 3))
		    cval = (cval * 10) + (*cp++ - '0');
	}
	else if (*cp == '\\')		/* C-style character escapes */
	{
	    switch (*++cp)
	    {
	    case '\\': cval = '\\'; break;
	    case 'n': cval = '\n'; break;
	    case 't': cval = '\t'; break;
	    case 'b': cval = '\b'; break;
	    case 'r': cval = '\r'; break;
	    default: cval = *cp;
	    }
	    cp++;
	}
	else if (*cp == '^')		/* expand control-character syntax */
	{
	    cval = (*++cp & 0x1f);
	    cp++;
	}
	else
	    cval = *cp++;
	*tp++ = cval;
    }

    return(tp - StartAddr);
}

/* end */

