	GIF viewer

	This is a WWW applet (conforming to the W3A API) for viewing
	images in GIF format. Apart from showing the image, it also
	accepts mouse clicks, which it passes to the browser by means
	of the [[W3Aevent]] function.

	Note that this viewer is not really interested in the
	information about the document that it is viewing. It simply
	ignores the [[W3ADocumentInfo]] that is passed to [[openGIF]],
	except that it keeps the URL for use in a possible error
	message.

	A mouse click causes a call to [[W3Aevent]], with event type
	5000 and a pointer to a pair (x,y) of integers as parameter.


	The GIF viewer draws directly into the widget that is provided
	by the main browser. Since it is not known what kind of widget
	it is (probably Composite or XmManager), the GIF viewer
	assumes only that it is a subclass of Core. It installs a set
	of translations and actions to override whatever the widget
	already has.

	The action functions are registered only once, when the GIF
	viewer is initialized ([[initGIF]]). The translations are
	installed in every widget that is passed to [[openGIF]].

	The action functions have a single parameter, which is used to
	pass the Buffer with info about the image to the action
	function. Since action functions can only have string
	arguments, the pointer to the Buffer is encoded as a decimal
	number..


	NB. This viewer is easily generalized to more image types. It
	only needs more image decoding functions.


	TO DO: When the image uses only (almost) gray colors, the
	standard gray colormap shouldbe used instead of the default map.


	Acknowledgments

	This software incorporates algorithms from several sources, as
	well as a few that are my own invention. It makes use of the
	XSCM library (by Peter Kaczowka <kaczowka@ch.hp.com>,
	copyright Hewlett Packard 1994). The algorithm for splitting
	off a number of bits from the stream of bytes (using
	`workbits') is taken from `giflib' by Kirk L. Johnson (1989,
	1990). The idea to loop over the LZW codes and call out to
	`store_pixel' instead of looping over the image coordinates
	and calling `next_code', is taken from `lug-tools' by Paul
	Rivero (8th Jan 1992).

	Bert Bos <bert@let.rug.nl>, 11 Aug 1994

<<*>>=
#include <config.h>
#include <w3a.h>
#include <str.h>
#include <Xm/Xm.h>
#include <X11/extensions/shape.h>
#include <xscmlib.h>

#define LIVE_IMAGES 1

/* Defined in w3a.h: */
/* #define POINT_SELECT 5000			/* W3A event type */

#define CMAP 256				/* Max. GIF color table */
#define MAX_LZW_BITS 12                         /* Code can grow to this */
#define MAXSTACK 4096                           /* Maximum with 12 bits */

#define GIF_SUCCESS 0                           /* Decoded successfully */
#define NO_GIF 1                                /* Wrong magic number */
#define SHORT_DATA 2                            /* Unexpected end of data */
#define IMG_START 3                             /* Image seperator missing */

#define GRAYMARGIN 10000			/* Max. distance from grayrey */

#define abs(a) ((a) < 0 ? -(a) : (a))
#define min(a, b) ((a) < (b) ? (a) : (b))

static char *err_msg[] = {
    "OK",
    "GIF viewer failed: image is not a GIF87a or GIF89a\n(%s)",
    "GIF viewer failed: image is incomplete\n(%s)",
    "GIF viewer failed: start of image not found\n(%s)",
};

typedef struct {                                /* A viewer instance */
    char *url;                                  /* Only used for error msg */
    unsigned char buf[2*BUFSIZ];		/* Temporary GIF image data */
    size_t buflen;                              /* Length of buf in bytes */
    Widget canvas;                              /* The widget to draw into */
    GC gc;                                      /* GC for image */
    int width, height;				/* Image size */
    int red[CMAP], green[CMAP], blue[CMAP];	/* Color map table */
    Bool use_gray;				/* Use std. grayrey map? */
    int transparent;				/* Transp. color index or -1 */
    int xpos, ypos, step, pass;			/* Coords. & interlace step */
    int rowstart;				/* == ypos * width */
    int maskrowstart;				/* == ypos * ((width+7)/8) */
    char *mask;					/* Transparency mask */
    unsigned char *image;			/* Decoded image */

    int state;
    Bool ready;					/* Image fully decoded */
    Bool has_color_table;
    int color_table_size;
    int clear_code, eoi_code, min_code_size, free_code;
    int nrworkbits, code_size, code_mask;
    int prev_code, bufidx, blocklen, first;
    int prefix[MAXSTACK], extnsn[MAXSTACK];
    unsigned long workbits;
} *Buffer;

#define discard(b, n) memmove((b)->buf, (b)->buf + (n), (b)->buflen -= (n))

static XscmInfoRec xscm_info, xscm_gray_info;
static Bool default_stdcmap, gray_stdcmap;
@

	To associate IDs with Buffers, a simple linear list is used.

<<*>>=
typedef struct _Assoc {long id; Buffer b; struct _Assoc *next;} *Assoc;

static Assoc assoclist = NULL;

/* store -- store an ID/Buffer combination */
static void store(Buffer b, long id)
{
    Assoc h;
    new(h); h->id = id; h->b = b; h->next = assoclist; assoclist = h;
}

/* delete -- delete an ID/Buffer combination */
static void delete(long id)
{
    Assoc g, h;
    assert(assoclist);
    if (assoclist->id == id) {
	h = assoclist; assoclist = assoclist->next; dispose(h);
    } else {
	assert(assoclist->next);
	for (h = assoclist; h->next->id != id; h = h->next) assert(h->next);
	g = h->next; h->next = g->next; dispose(g);
    }
}

/* find -- find the Buffer associated with an ID */
static Buffer find(long id)
{
    Assoc h;
    assert(assoclist);
    for (h = assoclist; h->id != id; h = h->next) assert(h->next);
    return h->b;
}
@

	[[index]] is an index into the image's color table. It is
	passed as an [[int]] for convenience, but it should be in the
	range 0..255. It is added to the decoded image at the current
	coordinates.

	If [[index]] indicates the transparent color, a bit is cleared
	in the mask bitmap.

<<*>>=
/* store_pixel -- add pixel to decoded image */
static void store_pixel(Buffer b, int index)
{
    static int stepsize[] = {8, 8, 4, 2, 0};
    static int startrow[] = {0, 4, 2, 1, 0};

    assert(0 <= index && index <= 255);
    assert(b->rowstart == b->ypos * b->width);
    assert(b->maskrowstart == b->ypos * ((b->width + 7)/8));

    if (index == b->transparent)
	b->mask[b->maskrowstart + b->xpos/8] &= ~(1 << (b->xpos % 8));

    b->image[b->rowstart + b->xpos] = index;

    if (++b->xpos == b->width) {		/* At the end of a row... */
#if LIVE_IMAGES
	if (XtIsRealized(b->canvas)) {		/* ...display 1 screen row */
	    if (gray_stdcmap && (b->use_gray || ! default_stdcmap))
		XscmDisplay(&xscm_gray_info, b->image + b->ypos * b->width,
			    b->width, b->width, 1, b->red, b->green,
			    b->blue, XtWindow(b->canvas), b->gc, 0, b->ypos);
	    else if (default_stdcmap)
		XscmDisplay(&xscm_info, b->image + b->ypos * b->width,
			    b->width, b->width, 1, b->red, b->green,
			    b->blue, XtWindow(b->canvas), b->gc, 0, b->ypos);
	}
#endif /* LIVE_IMAGES */
	b->xpos = 0;
	b->ypos += b->step;
	b->rowstart += b->step * b->width;
	b->maskrowstart += b->step * ((b->width + 7)/8);
	if (b->ypos >= b->height) {
	    b->pass++;
	    b->step = stepsize[b->pass];
	    b->ypos = startrow[b->pass];
	    b->rowstart = b->ypos * b->width;
	    b->maskrowstart = b->ypos * ((b->width + 7)/8);
	}
    }
}
@

	The routine works as a co-routine. The large switch represents
	the various entry points. Whenever a bit of the GIF data has
	been parsed, a state variable is incremented. When the
	routine has to stop due to insufficient data, it can resume
	where it left off by inspecting the state.

<<*>>=
/* parseGIF -- decode GIF image data and create an XImage from it */
static int parseGIF(Buffer b)
{
    int stack[MAXSTACK];
    Bool is_interlaced;
    Dimension dum1, dum2;			/* Dummy arguments */
    int i, j, drb, dgb, drg;
    int code, sp = 0;

    for (;;) {					/* Interrupted by a return */
	switch (b->state) {
	case 0:					/* Initialize */
	    b->transparent = -1;
	    b->state++;
	    /* Fall through */
	case 1:					/* Read magic number */
	    if (b->buflen < 6)			/* Insufficient data */
		return TRUE;
	    if (!memcmp(b->buf, "GIF87a", 6) && !memcmp(b->buf, "GIF89a", 6))
		return FALSE;			/* Error */
	    discard(b, 6);
	    b->state++;
	    /* Fall through */
	case 2:					/* Logical Screen Descriptor */
	    if (b->buflen < 7) return TRUE;
	    b->has_color_table = (b->buf[4] & 0x80) == 0x80;
	    b->color_table_size = 2 << (b->buf[4] & 0x07);
	    discard(b, 7);
	    b->state++;
	    /* Fall through */
	case 3:					/* Global Color Table */
	    if (b->has_color_table) {
		if (b->buflen < 3 * b->color_table_size) return TRUE;
		b->use_gray = TRUE;
		for (i = 0, j = 0; i < b->color_table_size; i++) {
		    /* Expand 0..255 --> 0..65535 */
 		    b->red[i] = ((int) b->buf[j++]) << 8;
		    b->green[i] = ((int) b->buf[j++]) << 8;
		    b->blue[i] = ((int) b->buf[j++]) << 8;
		    if (b->use_gray) {
			drb = abs(b->blue[i] - b->red[i]);
			dgb = abs(b->blue[i] - b->green[i]);
			drg = abs(b->green[i] - b->red[i]);
			if (drb > GRAYMARGIN || dgb > GRAYMARGIN
			    || drg > GRAYMARGIN) b->use_gray = FALSE;
		    }
		}
	    }
	    discard(b, 3 * b->color_table_size);
	    b->state++;
	    /* Fall through */
	case 4:					/* Extensions */
	    if (b->buflen < 1) return TRUE;
	    if (b->buf[0] == 0x21) {
		if (b->buflen < 2) return TRUE;
		else if (b->buf[1] == 0xf9) b->state = 400;
		else b->state = 410;
		discard(b, 2);
		break;
	    }
	    b->state++;
	    /* Fall through */
	case 5:					/* Image Descriptor */
	    if (b->buflen < 10) return TRUE;
	    if (b->buf[0] != 0x2c) return FALSE; /* No Image Seperator */
	    b->width = (b->buf[6] << 8) | b->buf[5];
	    b->height = (b->buf[8] << 8) | b->buf[7];
	    b->has_color_table = (b->buf[9] & 0x80) == 0x80;
	    is_interlaced = (b->buf[9] & 0x40) == 0x40;
	    b->color_table_size = 2 << (b->buf[9] & 0x07);
	    b->xpos = 0;
	    b->ypos = 0;
	    b->step = is_interlaced ? 8 : 1;
	    b->pass = is_interlaced ? 0 : 3;
	    b->rowstart = 0;
	    b->maskrowstart = 0;
	    newarray(b->image, b->height * b->width);
	    if (b->transparent >= 0) {
		newarray(b->mask, b->height * ((b->width + 7)/8));
		memset(b->mask, 0xff, b->height * ((b->width + 7)/8));
	    }
#if 1
	    XtMakeResizeRequest(b->canvas, b->width, b->height, &dum1, &dum2);
#endif
	    discard(b, 10);
	    b->state++;
	    /* Fall through */
	case 6:					/* Local Color Table */
	    if (b->has_color_table) {
		if (b->buflen < 3 * b->color_table_size) return TRUE;
		b->use_gray = TRUE;
		for (i = 0, j = 0; i < b->color_table_size; i++) {
		    /* Expand 0..255 --> 0..65535 */
 		    b->red[i] = ((int) b->buf[j++]) << 8;
		    b->green[i] = ((int) b->buf[j++]) << 8;
		    b->blue[i] = ((int) b->buf[j++]) << 8;
		    if (b->use_gray) {
			drb = abs(b->blue[i] - b->red[i]);
			dgb = abs(b->blue[i] - b->green[i]);
			drg = abs(b->green[i] - b->red[i]);
			if (drb > GRAYMARGIN || dgb > GRAYMARGIN
			    || drg > GRAYMARGIN) b->use_gray = FALSE;
		    }
		}
	    }
	    b->state++;
	    /* Fall through */
	case 7:					/* Special codes */
	    assert(b->state == 7);
	    if (b->buflen < 1) return TRUE;
	    b->min_code_size = b->buf[0];
	    b->clear_code = 1 << b->min_code_size;
	    b->eoi_code = b->clear_code + 1;
	    b->code_size = b->min_code_size + 1;
	    b->free_code = b->clear_code + 2;
	    b->code_mask = (1 << b->code_size) - 1;
	    b->nrworkbits = 0;
	    b->workbits = 0;
	    b->bufidx = 0;
	    b->blocklen = 0;
	    b->prev_code = -2;
	    discard(b, 1);
	    b->state++;
	case 8:					/* Decode LZW */
	    /* Add bytes until we have enough bits for next code */
	    while (b->nrworkbits < b->code_size) {

		/* Read new data block if needed */
		if (b->bufidx == b->blocklen) {
		    discard(b, b->blocklen);
		    b->bufidx = b->blocklen = 0;
		    if (b->buflen < 1 || b->buflen < 1 + (size_t) b->buf[0])
			return TRUE;
		    b->blocklen = 1 + b->buf[0];
		    b->bufidx = 1;
		}
		b->workbits |= (unsigned long) b->buf[b->bufidx++]
		    << b->nrworkbits;
		b->nrworkbits += 8;
	    }

	    /* Get next code */
	    code = b->workbits & b->code_mask;
	    b->workbits >>= b->code_size;
	    b->nrworkbits -= b->code_size;

	    /* Force first code of image to be a clear code */
	    if (b->prev_code == -2) code = b->clear_code;

	    /* Branch on type of code */
	    if (code == b->clear_code) {	/* Reset the decoder */
		b->code_size = b->min_code_size + 1; /* Original size */
		b->code_mask = (1 << b->code_size) - 1;
		b->free_code = b->clear_code + 2; /* First pos in tables */
		b->prev_code = -1;		/* Next code is a root code */
	    } else if (code == b->eoi_code) {	/* End of Information */
		b->state++;
		break;
	    } else if (b->prev_code == -1) {	/* 1st code after clearcode */
		store_pixel(b, code);		/* Add to image */
		b->first = b->prev_code = code;
	    } else {				/* We've got a normal code */
		assert(sp == 0);
		if (code >= b->free_code) {	/* It's a new code */
		    stack[sp++] = b->first;
		    b->first = b->prev_code;
		} else				/* It's an existing code */
		    b->first = code;
		while (b->first >= b->clear_code) { /* Push string of pixels */
		    stack[sp++] = b->extnsn[b->first];
		    b->first = b->prefix[b->first];
		}
		stack[sp++] = b->first;		/* Push string's root code */
		while (sp != 0)			/* Now add pixels to image */
		    store_pixel(b, stack[--sp]);
		b->prefix[b->free_code] = b->prev_code;
		b->extnsn[b->free_code++] = b->first;
		b->prev_code = code;

		/* Check if code_size needs to increase */
		if (b->free_code > b->code_mask
		    && b->code_size != MAX_LZW_BITS) {
		    b->code_size++;
		    b->code_mask = (1 << b->code_size) - 1;
		}
	    }
	    break;
	case 9:					/* Ignore additional images */
	    discard(b, b->buflen);
	    b->ready = TRUE;
	    return TRUE;
	    
	case 400:				/* Graphic Control Extension */
	    if (b->buflen < 6) return TRUE;
	    b->transparent = b->buf[4];		/* Index in colortable */
	    discard(b, 6);
	    b->state = 4;
	    break;
	case 410:				/* Other extension */
	    if (b->buflen < 1) return TRUE;
	    if (b->buf[0] == 0) {		/* Last block */
		discard(b, 1);
		b->state = 4;
	    } else if (b->buflen < 1 + (size_t) b->buf[0]) {
		return TRUE;
	    } else {
		discard(b, 1 + b->buf[0]); /* Remove one block */
	    }
	    break;

	default:
	    assert(! "Cannot happen");
	}
    }
}


/* GIF_redraw -- redraw the indicated area of a GIF image */
static void GIF_redraw2(Buffer b, int x, int y, int wd, int ht)
{
    Widget w = b->canvas;

#if 0
    XtMakeResizeRequest(b->canvas, b->width, b->height, &dum1, &dum2);
#endif

    /*
     * Only once: set the window shape
     */
    if (b->mask != NULL && b->ready) {
	Pixmap mask;
	mask = XCreateBitmapFromData(XtDisplay(w), XtWindow(w),
				     b->mask, b->width, b->height);
	if (mask != None) {
	    XShapeCombineMask(XtDisplay(w), XtWindow(w),
			      ShapeBounding, 0, 0, mask, ShapeSet);
	    XFreePixmap(XtDisplay(w), mask);
	}
	dispose(b->mask);
	b->mask = NULL;
    }

    /* Draw image to exposed rectangle */
    if (gray_stdcmap && (b->use_gray || ! default_stdcmap))
	XscmDisplay(&xscm_gray_info, b->image + y * b->width + x,
		    b->width, wd, ht, b->red, b->green, b->blue,
		    XtWindow(w), b->gc, x, y);
    else if (default_stdcmap)
	XscmDisplay(&xscm_info, b->image + y * b->width + x,
		    b->width, wd, ht, b->red, b->green, b->blue,
		    XtWindow(w), b->gc, x, y);
    else
	assert(! "Cannot happen");
}

/* GIF_redraw -- action proc: redraw the exposed area of a GIF image */
static void GIF_redraw(Widget w, XEvent *ev, String *parms, Cardinal *nparms)
{
    Buffer b;
    int wd, ht, x, y;

    /* Decode string argument as a Buffer pointer */
    assert(*nparms == 1);
    if (sscanf(parms[0], "%ld", &b) != 1) assert(!"Missing parameter");
    if (! XtIsRealized(w) || b->image == NULL) return;
    assert(b->canvas == w);

    x = ev ? ev->xexpose.x : 0;
    y = ev ? ev->xexpose.y : 0;
    if (x >= b->width || y >= b->height) return; /* Nothing to draw */
    wd = ev ? min(ev->xexpose.width, b->width - x) : b->width;
    ht = ev ? min(ev->xexpose.height, b->height - y) : b->height;

    GIF_redraw2(b, x, y, wd, ht);
}

/* GIF_click -- action for mouse clicks on canvas */
/* ARGSUSED */
static void GIF_click(Widget w, XEvent *ev, String *params, Cardinal *nparams)
{
    long id;
    struct {int x, y;} coords;

    assert(*nparams == 1);
    if (sscanf(params[0], "%ld", &id) != 1) assert(!"Missing parameter");
	coords.x = ev->xbutton.x;
	coords.y = ev->xbutton.y;
#if 0
    debug("Clicked on image: (%d,%d)\n", coords.x, coords.y);
#endif
    W3Aevent(id, POINT_SELECT, &coords);
#ifndef NDEBUG
    {
	Buffer b = find(id);
	assert(b->canvas == w);
    }
#endif
}


static XtActionsRec actions[] = {
    {"GIF_expose", GIF_redraw},
    {"GIF_click", GIF_click}
};

/*
 * The 1st %ld points to the Buffer, the 2nd is the ID
 */
static char translations[] =
    "<Expose>: GIF_expose(%ld)\n\
    <Btn1Down>,<Btn1Up>: GIF_click(%ld)";



/* initGIF -- initialize the GIF viewer class */
EXPORT Bool initGIF(char ***mime_types, int *nrtypes, float **prefs)
{
    static char *types[] = {"image/gif"};
    static float pref[] = {1.0};
    Widget toplevel = W3Atoplevel();
    Display *dpy = XtDisplay(toplevel);
    int screen = XScreenNumberOfScreen(XtScreen(toplevel));

    *mime_types = types;
    *nrtypes = 1;
    *prefs = pref;
    XtAppAddActions(XtWidgetToApplicationContext(toplevel),
		    actions, XtNumber(actions));
    default_stdcmap = XscmFindColorSCM(dpy, screen, XSCM_ANY, &xscm_info);
    gray_stdcmap = XscmFindGraySCM(dpy, screen, &xscm_gray_info);

    if (! default_stdcmap && ! gray_stdcmap)
	XtAppWarning(XtWidgetToApplicationContext(W3Atoplevel()),
		     "GIF viewer: Could find neither a standard color map \
nor a standard gray map");
    return default_stdcmap || gray_stdcmap;
}


/* openGIF -- start a new GIF viewer, return its ID */
EXPORT Bool openGIF(const W3ADocumentInfo doc, W3AWindow window, long id)
{
    int screen = XScreenNumberOfScreen(XtScreen(window));
    Display *dpy = XtDisplay(window);
    char s[256];
    Buffer b;

    new(b);
    store(b, id);
    b->url = newstring(doc.url);
    b->buflen = 0;
    b->image = NULL;
    b->mask = NULL;
    b->canvas = window;
    b->state = 0;
    b->ready = FALSE;
    sprintf(s, translations, b, id);
    XtOverrideTranslations(b->canvas, XtParseTranslationTable(s));
    b->gc = DefaultGC(dpy, screen);
    return TRUE;
}


/* writeGIF -- add image data to the buffer, decode when complete */
EXPORT int writeGIF(long id, const char *buf, size_t nbytes)
{
    Buffer b = find(id);
    int n, rest = nbytes;

    while (rest) {
	n = min(rest, sizeof(b->buf) - b->buflen);
	memcpy(b->buf + b->buflen, buf, n);
	b->buflen += n;
	rest -= n;
	buf += n;
	if (! parseGIF(b)) {errno = EFORMAT; return -1;}
    }
    /* When image complete, redraw it, to correct the dithering */
    if (nbytes == 0 && XtIsRealized(b->canvas)) {
#if 0
	XClearArea(XtDisplay(b->canvas), XtWindow(b->canvas),
		   0, 0, 0, 0, TRUE);
#else
	GIF_redraw2(b, 0, 0, b->width, b->height);
#endif
    }
    return nbytes;
}


/* closeGIF -- close a GIF viewer */
EXPORT Bool closeGIF(long id)
{
    Buffer b = find(id);

    dispose(b->url);
    dispose(b->image);
    delete(id);
    return TRUE;
}


/* eventGIF -- react to events happening elsewhere */
/* ARGSUSED */
EXPORT void eventGIF(long id, long source, long eventtype, void *params)
{
#if 0
    debug("GIF viewer %ld received event %ld from %ld\n",
	  id, eventtype, source);
#endif
    /* Doesn't handle events */
}


/* infoGIF -- a chance to modify the document info based on the contents */
/* ARGSUSED */
EXPORT Bool infoGIF(long id, W3ADocumentInfo *doc)
{
    /* No modifications */
    return TRUE;
}
