/* xpmtoppm.c - read an X11 pixmap file and produce a portable pixmap
**
** Copyright (C) 1991 by Jef Poskanzer.
**
** Permission to use, copy, modify, and distribute this software and its
** documentation for any purpose and without fee is hereby granted, provided
** that the above copyright notice appear in all copies and that both that
** copyright notice and this permission notice appear in supporting
** documentation.  This software is provided "as is" without express or
** implied warranty.
**
** Upgraded to support XPM version 3 by
**   Arnaud Le Hors (lehors@mirsa.inria.fr)
**   Tue Apr 9 1991
**
** Rainer Sinkwitz sinkwitz@ifi.unizh.ch - 21 Nov 91:
**  - Bug fix, no advance of read ptr, would not read 
**    colors like "ac c black" because it would find 
**    the "c" of "ac" and then had problems with "c"
**    as color.
**    
**  - Now understands multword X11 color names
**  
**  - Now reads multiple color keys. Takes the color
**    of the hightest available key. Lines no longer need
**    to begin with key 'c'.
**    
**  - expanded line buffer to from 500 to 2048 for bigger files
*/

#include "ppm.h"

static void ReadXPMFile ARGS((FILE *stream, int *widthP, int *heightP,
    int *ncolorsP, int *chars_per_pixelP, pixel **colorsP, int **dataP));
static void getline ARGS((char *line, int size, FILE *stream));

/* number of xpmColorKeys */
#define NKEYS 5

char *xpmColorKeys[] =
{
 "s",					/* key #1: symbol */
 "m",					/* key #2: mono visual */
 "g4",					/* key #3: 4 grays visual */
 "g",					/* key #4: gray visual */
 "c",					/* key #5: color visual */
};

int
main(argc, argv)
    int argc;
    char *argv[];

{
    FILE *ifp;
    pixel *pixrow, *colors;
    register pixel *pP;
    int rows, cols, ncolors, chars_per_pixel, row;
    register int col;
    int *data;
    register int *ptr;

    ppm_init(&argc, argv);

    if (argc > 2)
	pm_usage("[xpmfile]");

    if (argc == 2)
	ifp = pm_openr(argv[1]);
    else
	ifp = stdin;

    ReadXPMFile(
	    ifp, &cols, &rows, &ncolors, &chars_per_pixel, &colors, &data);

    pm_close(ifp);

    ppm_writeppminit(stdout, cols, rows, (pixval) PPM_MAXMAXVAL, 0);
    pixrow = ppm_allocrow(cols);

    for (row = 0, ptr = data; row < rows; ++row) {
	for (col = 0, pP = pixrow; col < cols; ++col, ++pP, ++ptr)
	    *pP = colors[*ptr];
	ppm_writeppmrow(stdout, pixrow, cols, (pixval) PPM_MAXMAXVAL, 0);
    }

    exit(0);
}

#define MAX_LINE 2048

static
void
ReadXPMFile(stream, widthP, heightP, ncolorsP,
	    chars_per_pixelP, colorsP, dataP)
    FILE *stream;
    int *widthP;
    int *heightP;
    int *ncolorsP;
    int *chars_per_pixelP;
    pixel **colorsP;
    int **dataP;
{
    char line[MAX_LINE], str1[MAX_LINE], str2[MAX_LINE];
    char *t1;
    char *t2;
    int format, v, datasize;
    int *ptr;
    int *ptab;
    register int i, j;
    int flag;

    unsigned int curkey, key, highkey;	/* current color key */
    unsigned int lastwaskey;		/* key read */
    char curbuf[BUFSIZ];		/* current buffer */

    *widthP = *heightP = *ncolorsP = *chars_per_pixelP = format = -1;
    flag = 0;				/* to avoid getting twice a line */

    /* First try to read as an XPM version 3 file */

    /* Read the header line */
    getline(line, sizeof(line), stream);
    if (sscanf(line, "/* %s */", str1) == 1
	&& !strncmp(str1, "XPM", 3)) {

	/* Read the assignment line */
	getline(line, sizeof(line), stream);
	if (strncmp(line, "static char", 11))
	    pm_error("error scanning assignment line", 0, 0, 0, 0, 0);

	/* Read the hints line */
	getline(line, sizeof(line), stream);
	/* skip the comment line if any */
	if (!strncmp(line, "/*", 2)) {
	    while (!strstr(line, "*/"))
		getline(line, sizeof(line), stream);
	    getline(line, sizeof(line), stream);
	}
	if (sscanf(line, "\"%d %d %d %d\",", widthP, heightP,
		   ncolorsP, chars_per_pixelP) != 4)
	    pm_error("error scanning hints line", 0, 0, 0, 0, 0);

	/* Allocate space for color table. */
	if (*chars_per_pixelP <= 2) {
	    /* Up to two chars per pixel, we can use an indexed table. */
	    v = 1;
	    for (i = 0; i < *chars_per_pixelP; ++i)
		v *= 256;
	    *colorsP = ppm_allocrow(v);
	} else {
	    /* Over two chars per pixel, we fall back on linear search. */
	    *colorsP = ppm_allocrow(*ncolorsP);
	    ptab = (int *) malloc(*ncolorsP * sizeof(int));
	}

	/* Read the color table */
	for (i = 0; i < *ncolorsP; i++) {
	    getline(line, sizeof(line), stream);
	    /* skip the comment line if any */
	    if (!strncmp(line, "/*", 2))
		getline(line, sizeof(line), stream);

	    /* read the chars */
	    if ((t1 = index(line, '"')) == NULL)
		pm_error("error scanning color table", 0, 0, 0, 0, 0);
	    else
		t1++;
	    strncpy(str1, t1, *chars_per_pixelP);
	    str1[*chars_per_pixelP] = '\0';
	    t1++; t1++;

	    v = 0;
	    for (j = 0; j < *chars_per_pixelP; ++j)
		v = (v << 8) + str1[j];
	    /*
	     * read color keys and values 
	     */
	    curkey = 0; 
	    highkey = 1;
	    lastwaskey = 0;
	    t2 = t1;
	    while ( 1 ) {
		for (t1=t2 ;; t1++)
		    if (*t1 != ' ' && *t1 != '	')
			break;
		for (t2 = t1;; t2++)
		    if (*t2 == ' ' || *t2 == '	' || *t2 == '"')
			break;
		if (t2 == t1) break;
		strncpy(str2, t1, t2 - t1);
		str2[t2 - t1] = '\0';
	        
		if (!lastwaskey) {
		    for (key = 1; key < NKEYS + 1; key++)
			if (!strcmp(xpmColorKeys[key - 1], str2))
			    break;
		} else 
		    key = NKEYS + 1;
		if (key > NKEYS) {			/* append name */
		    if (!curkey) 
			pm_error("error scanning color table", 0, 0, 0, 0, 0);
		    if (!lastwaskey) 
			strcat(curbuf, " ");		/* append space */
		    strcat(curbuf, str2);		/* append buf */
		    lastwaskey = 0;
		}
		if (key <= NKEYS) { 			/* new key */
		    if (curkey > highkey) {	/* flush string */
			if (*chars_per_pixelP <= 2)
			    /* Index into table. */
			    (*colorsP)[v] = ppm_parsecolor(curbuf,
						(pixval) PPM_MAXMAXVAL);
			else {
			    /* Set up linear search table. */
			    (*colorsP)[i] = ppm_parsecolor(curbuf,
						(pixval) PPM_MAXMAXVAL);
			    ptab[i] = v;
			}
			highkey = curkey;
		    }
		    curkey = key;			/* set new key  */
		    curbuf[0] = '\0';		/* reset curbuf */
		    lastwaskey = 1;
		}
	    if (*t2 == '"') break;
	    }
	    if (curkey > highkey) {
		if (*chars_per_pixelP <= 2)
		    /* Index into table. */
		    (*colorsP)[v] = ppm_parsecolor(curbuf,
					(pixval) PPM_MAXMAXVAL);
		else {
		    /* Set up linear search table. */
		    (*colorsP)[i] = ppm_parsecolor(curbuf,
					(pixval) PPM_MAXMAXVAL);
		    ptab[i] = v;
		}
		highkey = curkey;
	    }
	    if (highkey == 1) 
		pm_error("error scanning color table", 0, 0, 0, 0, 0);
	}
	/* Read pixels. */
	getline(line, sizeof(line), stream);
	/* skip the comment line if any */
	if (!strncmp(line, "/*", 2))
	    getline(line, sizeof(line), stream);

    } else {				/* try as an XPM version 1 file */

	/* Read the initial defines. */
	for (;;) {
	    if (flag)
		getline(line, sizeof(line), stream);
	    else
		flag++;

	    if (sscanf(line, "#define %s %d", str1, &v) == 2) {
		if ((t1 = rindex(str1, '_')) == NULL)
		    t1 = str1;
		else
		    ++t1;
		if (!strcmp(t1, "format"))
		    format = v;
		else if (!strcmp(t1, "width"))
		    *widthP = v;
		else if (!strcmp(t1, "height"))
		    *heightP = v;
		else if (!strcmp(t1, "ncolors"))
		    *ncolorsP = v;
		else if (!strcmp(t1, "pixel"))
		    *chars_per_pixelP = v;
	    } else if (!strncmp(line, "static char", 11)) {
		if ((t1 = rindex(line, '_')) == NULL)
		    t1 = line;
		else
		    ++t1;
		break;
	    }
	}
	if (format == -1)
	    pm_error("missing or invalid format", 0, 0, 0, 0, 0);
	if (format != 1)
	    pm_error("can't handle XPM version %d", format, 0, 0, 0, 0);
	if (*widthP == -1)
	    pm_error("missing or invalid width", 0, 0, 0, 0, 0);
	if (*heightP == -1)
	    pm_error("missing or invalid height", 0, 0, 0, 0, 0);
	if (*ncolorsP == -1)
	    pm_error("missing or invalid ncolors", 0, 0, 0, 0, 0);
	if (*chars_per_pixelP == -1)
	    pm_error("missing or invalid chars_per_pixel", 0, 0, 0, 0, 0);
	if (*chars_per_pixelP > 2)
	    pm_message("warning, chars_per_pixel > 2 uses a lot of memory"
		       ,0, 0, 0, 0, 0);

	/* If there's a monochrome color table, skip it. */
	if (!strncmp(t1, "mono", 4)) {
	    for (;;) {
		getline(line, sizeof(line), stream);
		if (!strncmp(line, "static char", 11))
		    break;
	    }
	}
	/* Allocate space for color table. */
	if (*chars_per_pixelP <= 2) {
	    /* Up to two chars per pixel, we can use an indexed table. */
	    v = 1;
	    for (i = 0; i < *chars_per_pixelP; ++i)
		v *= 256;
	    *colorsP = ppm_allocrow(v);
	} else {
	    /* Over two chars per pixel, we fall back on linear search. */
	    *colorsP = ppm_allocrow(*ncolorsP);
	    ptab = (int *) malloc(*ncolorsP * sizeof(int));
	}

	/* Read color table. */
	for (i = 0; i < *ncolorsP; ++i) {
	    getline(line, sizeof(line), stream);

	    if ((t1 = index(line, '"')) == NULL)
		pm_error("error scanning color table", 0, 0, 0, 0, 0);
	    if ((t2 = index(t1 + 1, '"')) == NULL)
		pm_error("error scanning color table", 0, 0, 0, 0, 0);
	    if (t2 - t1 - 1 != *chars_per_pixelP)
		pm_error("wrong number of chars per pixel in color table",
			 0, 0, 0, 0, 0);
	    strncpy(str1, t1 + 1, t2 - t1 - 1);
	    str1[t2 - t1 - 1] = '\0';

	    if ((t1 = index(t2 + 1, '"')) == NULL)
		pm_error("error scanning color table", 0, 0, 0, 0, 0);
	    if ((t2 = index(t1 + 1, '"')) == NULL)
		pm_error("error scanning color table", 0, 0, 0, 0, 0);
	    strncpy(str2, t1 + 1, t2 - t1 - 1);
	    str2[t2 - t1 - 1] = '\0';

	    v = 0;
	    for (j = 0; j < *chars_per_pixelP; ++j)
		v = (v << 8) + str1[j];
	    if (*chars_per_pixelP <= 2)
		/* Index into table. */
		(*colorsP)[v] = ppm_parsecolor(str2,
					       (pixval) PPM_MAXMAXVAL);
	    else {
		/* Set up linear search table. */
		(*colorsP)[i] = ppm_parsecolor(str2,
					       (pixval) PPM_MAXMAXVAL);
		ptab[i] = v;
	    }
	}

	/* Read pixels. */
	for (;;) {
	    getline(line, sizeof(line), stream);
	    if (!strncmp(line, "static char", 11))
		break;
	}
    }
    datasize = *widthP * *heightP;
    *dataP = (int *) malloc(datasize * sizeof(int));
    if (*dataP == 0)
	pm_error("out of memory", 0, 0, 0, 0, 0);
    i = 0;
    ptr = *dataP;
    for (;;) {
	if (flag)
	    getline(line, sizeof(line), stream);
	else
	    flag++;

	/* Find the open quote. */
	if ((t1 = index(line, '"')) == NULL)
	    pm_error("error scanning pixels", 0, 0, 0, 0, 0);
	++t1;

	/* Handle pixels until a close quote or the end of the image. */
	while (*t1 != '"') {
	    v = 0;
	    for (j = 0; j < *chars_per_pixelP; ++j)
		v = (v << 8) + *t1++;
	    if (*chars_per_pixelP <= 2)
		/* Index into table. */
		*ptr++ = v;
	    else {
		/* Linear search into table. */
		for (j = 0; j < *ncolorsP; ++j)
		    if (ptab[j] == v)
			goto gotit;
		pm_error("unrecognized pixel in line \"%s\"", line,
			 0, 0, 0, 0);
	gotit:
		*ptr++ = j;
	    }
	    ++i;
	    if (i >= datasize)
		return;
	}
    }
}


static void
getline(line, size, stream)
    char *line;
    int size;
    FILE *stream;
{
    if (fgets(line, MAX_LINE, stream) == NULL)
	pm_error("EOF / read error", 0, 0, 0, 0, 0);
    if (strlen(line) == MAX_LINE - 1)
	pm_error("line too long", 0, 0, 0, 0, 0);
}
