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

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

SPDX-License-Identifier: MIT

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

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

#include "getarg.h"
#include "gif_lib.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[] = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNO"
                           "PQRSTUVWXYZ!\"#$%&'()*+,-./:<=>?@[\\]^_`{|}~";
#define PRINTABLES (sizeof(KeyLetters) - 1)

static void Icon2Gif(char *FileName, FILE *txtin, bool, 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,
	            GifNoisyPrint = 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, GifNoisyPrint, 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, GifNoisyPrint, 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, bool GifNoisyPrint,
                     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) {
			if (ColorMapSize >= 256) {
				PARSE_ERROR("Too many color entries.");
				exit(EXIT_FAILURE);
			}
			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) {
			if (ColorMapSize >= 256) {
				PARSE_ERROR("Too many color entries.");
				exit(EXIT_FAILURE);
			}
			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) {
			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.");
				// cppcheck-suppress knownConditionTrueFalse
				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;

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

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

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

			for (CopyFrom = Inclusion->SavedImages;
			     CopyFrom <
			     Inclusion->SavedImages + Inclusion->ImageCount;
			     CopyFrom++) {
				if ((NewImage = GifMakeSavedImage(
				         GifFileOut, CopyFrom)) == NULL) {
					PARSE_ERROR("Inclusion failed --- out "
					            "of memory.");
					// cppcheck-suppress nullPointerRedundantCheck
					PrintGifError(GifFileOut->Error);
					// cppcheck-suppress knownConditionTrueFalse
					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;
			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);

			GifByteType *tp = 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);
						*tp++ = (hi << 4) | lo;
					} else if ((dp = strchr(KeyTable, c))) {
						*tp++ = (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 */
