/*
 * Copyright (C) 1997 and 1998 WIDE Project.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of the project nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */
/*
 * $Id: draw.c,v 1.2 2001-03-14 12:09:57 thep Exp $
 */

#include "mgp.h"

/* state associated with the window - how should we treat this? */
static struct ctrl *bg_ctl, *bg_ctl_last, *bg_ctl_cache;
static int bgindex = 0;
struct render_state cache_state;

static u_short kinsokutable[] = {
	0x2121, 0x2122, 0x2123, 0x2124, 0x2125, 0x2126, 0x2127, 0x2128,
	0x2129, 0x212a, 0x212b, 0x212c, 0x212d, 0x212e, 0x212f, 0x2130,
	0x2133, 0x2134, 0x2135, 0x2136, 0x213c, 0x2147, 0x2149, 0x214b,
	0x214d, 0x214f, 0x2151, 0x2153, 0x2155, 0x2157, 0x2159, 0x216b,
	0x2242, 0x2244, 0
};

static struct pcache {
	u_int flag;
	u_int page;
	u_int mgpflag;
	u_int mode;
	u_int effect;
	u_int value;
} pcache;

#define	COMPLEX_BGIMAGE \
    (bg_ctl		\
     && ((bg_ctl->ct_op == CTL_BIMAGE)	\
       || bg_ctl->ct_op == CTL_BGRAD))

#define	COMPLEX_BGIMAGE2 (0)

#define	POSY(size)	(-(int)((size)/2))

static void process_direc __P((struct render_state *, int *));

static int set_position __P((struct render_state *));
static void draw_line_start __P((struct render_state *));
static void draw_line_itemsize __P((struct render_state *, int, int));
static void draw_line_output __P((struct render_state *, char *));
static void draw_line_end __P((struct render_state *));
static void cutin __P((struct render_state *, int, int, int));
#if 0
static void shrink __P((char *, u_int));
#endif

static void draw_string __P((struct render_state *, char *));
static void draw_fragment __P((struct render_state *, u_char *, u_int, char *, int));
static int iskinsokuchar __P((u_int));
static struct render_object *obj_alloc __P((struct render_state *state));
static void obj_free __P((struct render_state *, struct render_object *));
static int obj_new_xfont __P((struct render_state *, int, int, int,
	u_int, char *));
static int obj_new_image __P((struct render_state *, int, int, Image *, int, int));
static int obj_new_icon __P((struct render_state *, int, int, u_int, u_int, u_long, u_int, XPoint *));
static Pixel obj_image_color __P((Image *, Image *, Pixel, int *));
static Image *obj_image_trans __P((Image *, u_int, u_int));
static void obj_draw_image __P((Drawable, u_int, u_int, struct render_object *));
static void obj_draw __P((struct render_state *, Drawable, u_int, u_int));
#ifdef VFLIB
static int obj_new_vfont __P((struct render_state *, int, int, struct vfont *,
	int));
static u_int draw_onechar_vf __P((struct render_state *, u_int, int, int, u_int, u_int));
#endif
#ifdef FREETYPE
static u_int draw_onechar_tf __P((struct render_state *, u_int, int, int,
	u_int, char *, int, int));
#endif
static char *x_fontname __P((char *, int, char *, int, char *));
static int x_parsefont __P((char *, int *, int*));
static XFontStruct *x_setfont __P((char *, u_int, char *, int *));
static u_int draw_onechar_x __P((struct render_state *, u_int, int, int, int,
	char *, int));

static void back_gradation __P((struct render_state *, struct ctrl_grad *));
static void image_load __P((struct render_state *, char *, int, int, int, int, int, int));
static void image_load_ps __P((struct render_state *, char *, int, int, int, int, int, int));
static void process_icon __P((struct render_state *, struct ctrl *));
static void draw_bar __P((struct render_state *, struct ctrl *));
static void process_system __P((struct render_state *, struct ctrl *));
static void process_xsystem __P((struct render_state *, struct ctrl *));
static Window search_child_window __P(());
static void reparent_child_window __P((Window, int, int));
static char *epstoimage __P((struct render_state *, char *, int, int, int,
	int, int, int));
static void image_setcolor __P((struct render_state *));
static void x_registerseed __P((struct render_state *, char *, char *));
static char *x_findseed __P((struct render_state *, char *));

static void XClearPixmap __P((Display *, Drawable));
static void cache_page __P((struct render_state *, int));
static void cache_effect1 __P((void));
static void cache_effect2 __P((void));
static void set_from_cache __P((struct render_state *));
static void pcache_process __P((int));
static void predraw __P((struct render_state *));
static void set_background_pixmap __P((struct ctrl *));
static void get_background_pixmap __P((struct ctrl *, struct render_state *));
static void regist_background_pixmap __P((XImageInfo *, Image *));

#define CHECK_CACHE {if (caching){caching = -1; return;}}

static int
ispsfilename(p0)
	char *p0;
{
	char *p;

	p = p0;
	while (*p)
		p++;
	if (4 < p - p0 && strcasecmp(p - 4, ".eps") == 0)
		return 1;
	if (3 < p - p0 && strcasecmp(p - 3, ".ps") == 0)
		return 1;
	if (6 < p - p0 && strcasecmp(p - 6, ".idraw") == 0)
		return 1;
	return 0;
}

/*
 * state management.
 */
void
state_goto(state, page, repaint)
	struct render_state *state;
	u_int page;
	int repaint;
{
	if (!repaint)
		purgechild(state->page);

	state->page = page;
	state->line = 0;
	state->cp = NULL;
	state->phase = P_NONE;
	free_alloc_colors(&image_clr);
	free_alloc_colors(&font_clr);

#ifdef COLOR_BUGFIX
	colormap = XCopyColormapAndFree(display, colormap);
#endif
	predraw(state);
}

void
state_next(state)
	struct render_state *state;
{

	switch (state->phase) {
	case P_NONE:
		fprintf(stderr, "internal error\n");
		break;
	case P_DEFAULT:
		if (state->cp)
			state->cp = state->cp->ct_next;
		if (!state->cp) {
			state->cp = page_control[state->page][state->line];
			state->phase = P_PAGE;
		}
		break;
	case P_PAGE:
		if (state->cp)
			state->cp = state->cp->ct_next;
		if (!state->cp) {
			state->line++;
			state->cp = NULL;
			state->phase = P_NONE;
			state_init(state);
		}
		break;

	case P_END:
		/*nothing*/
		break;
	}

	/* next page */
	if (page_attribute[state->page].pg_linenum < state->line) {
		if (state->page < maxpage) {
			purgechild(state->page);
			if (mgp_flag & FL_FRDCACHE &&
				cached_page == state->page + 1){
					/* Hit cache */
					set_from_cache(state);
					pcache_process(state->page);
					cache_hit = 1;
			} else {
				state->phase = P_NONE;
				state->page++;
				state->line = 0;
				state_newpage(state);
				state_init(state);
			}
		} else
			state->phase = P_END;
	}
}

void
state_init(state)
	struct render_state *state;
{
	assert(state);

	if (state->phase == P_NONE || !state->cp) {
#if 0
		if (!(page_attribute[state->page].pg_flag & PGFLAG_NODEF)) {
			state->cp = default_control[state->line];
			state->phase = P_DEFAULT;
		} else
#endif
		{
			state->cp = page_control[state->page][state->line];
			state->phase = P_PAGE;
		}
	}
}

void
state_newpage(state)
	struct render_state *state;
{
	state->ypos = 0;
	state->have_mark = 0;
	free_alloc_colors(&image_clr);
	free_alloc_colors(&font_clr);

#ifdef COLOR_BUGFIX
	colormap = XCopyColormapAndFree(display, colormap);
#endif
	predraw(state);
}

/*
 * page management.
 */
void
draw_page(state, lastcp)
	struct render_state *state;
	struct ctrl *lastcp;
{
	u_int end_line;
	int pause;

	assert(state);

	/* initialize the state, if required. */
	if (state->phase != P_END && (state->phase == P_NONE || !state->cp)) {
		state_newpage(state);
		state_init(state);
	}

	end_line = page_attribute[state->page].pg_linenum;

	while (1) {
		switch (state->phase) {
		case P_NONE:
			fprintf(stderr, "internal error\n");
			cleanup(-1);
		case P_DEFAULT:
		case P_PAGE:
			pause = 0;
			if (state->cp)
				process_direc(state, &pause);
			if (caching == -1){
				/* caching failed */
				caching = 0;
				return;
			}
			if (lastcp && state->cp == lastcp)
				goto done;
			if (pause) {
				if (state->cp
				 && state->cp->ct_op == CTL_PAUSE
				 && state->cp->cti_value) {
					goto done;
				}
			}
			break;
		case P_END:
			goto done;
		}
#if 0
		XFlush(display);
#endif
		state_next(state);
	}
done:
	XFlush(display);
}

Bool
draw_one(state, e)
	struct render_state *state;
	XEvent *e;
{
	u_int end_line;
	int pause;
	fd_set fds;
	int xfd;
	struct timeval tout;
	long emask;
#ifdef TTY_KEYINPUT
	KeySym ks;
	char c;
	extern volatile int ttykey_enable;
#endif

	assert(state);

	/* initialize the state, if required. */
	if (state->phase != P_END && (state->phase == P_NONE || !state->cp)) {
		state_newpage(state);
		state_init(state);
	}

	end_line = page_attribute[state->page].pg_linenum;

	switch (state->phase) {
	case P_DEFAULT:
	case P_PAGE:
		pause = 0;
		if (state->cp)
			process_direc(state, &pause);
		break;
	case P_END:
		break;
	case P_NONE:
	default:
		fprintf(stderr, "internal error\n");
		cleanup(-1);
	}
	xfd = ConnectionNumber(display);
	if (state->phase != P_END && !pause)
		emask = xeventmask;
	else
		emask = ~NoEventMask;
	for (;;) {
		if (XCheckMaskEvent(display, emask, e) == True) {
			/* we got some event in the queue*/
			if (2 <= parse_debug) {
				fprintf(stderr,
					"interrupted and "
					"got X11 event type=%d\n",
					e->type);
			}
  got_event:
			if (state->phase == P_END)
				XFlush(display);
			else if (!pause)
				state_next(state);
			return True;
		}
#ifdef TTY_KEYINPUT
		if (ttykey_enable) {
			FD_ZERO(&fds);
			FD_SET(0, &fds);
			tout.tv_sec = tout.tv_usec = 0;
			if (select(1, &fds, NULL, NULL, &tout) > 0
			&&  read(0, &c, sizeof(c)) == sizeof(c)) {
				if (c > 0 && c < ' ')
					ks = 0xff00 | c;
				else if (c >= ' ' && c < '\177')
					ks = c;
				else if (c == '\177')
					ks = XK_Delete;
				else
					continue;
				e->xkey.display = display;
				e->xkey.type = KeyPress;
				e->xkey.keycode = XKeysymToKeycode(display, ks);
				if (e->xkey.keycode == 0)
					continue;
				goto got_event;
			}
		}
#endif
		if (state->phase != P_END && !pause) {
			state_next(state);
			return False;
		}
		FD_ZERO(&fds);
		FD_SET(xfd, &fds);
#ifdef TTY_KEYINPUT
		if (ttykey_enable)
			FD_SET(0, &fds);
#endif
		remapchild();
		/* always cache next page */
		if ((mgp_flag & FL_FRDCACHE) && cache_mode){
			cache_page(&cache_state, state->page +1);
			/* check if we got some events during caching */
			if (XCheckMaskEvent(display, emask, e) == True) 
  				goto got_event;
		}

		/* wait for something */
		tout.tv_sec = 2;
		tout.tv_usec = 0;
		(void)select(xfd + 1, &fds, NULL, NULL, &tout);

#ifdef TTY_KEYINPUT
		if (!(mgp_flag & FL_NOSTDIN) && !ttykey_enable)
			try_enable_ttykey();
#endif
		/* we have no event in 2sec, so..*/
		if (!FD_ISSET(xfd, &fds)){
			if ((mgp_flag & FL_FRDCACHE) && !cache_mode)
				cache_page(&cache_state, state->page +1);
			timebar(state);
		}
	}
	/*NOTREACHED*/
}

static void
process_direc(state, seenpause)
	struct render_state *state;
	int *seenpause;
{
	struct ctrl *cp;

	if (seenpause)
		*seenpause = 0;
	cp = state->cp;

	if (2 <= parse_debug) {
		fprintf(stderr, "p%d/l%d: ", state->page, state->line);
		debug0(cp);
	}

	switch(cp->ct_op) {
	case CTL_SIZE:
		char_size[caching] = state->height * cp->ctf_value / 100;
#ifdef FREETYPE
		tfc_setsize(char_size[caching]);
#endif
		break;

	case CTL_VGAP:
		vert_gap[caching] = cp->cti_value;
		break;

	case CTL_HGAP:
		horiz_gap[caching] = cp->cti_value;
		break;

	case CTL_GAP:
		vert_gap[caching] = horiz_gap[caching] = cp->cti_value;
		break;

	case CTL_QUALITY:
		if (!quality_flag)
			b_quality[caching] = cp->cti_value;
		break;

	case CTL_PAUSE:
		CHECK_CACHE
		if (seenpause)
			*seenpause = 1;
		break;

	case CTL_AGAIN:
		CHECK_CACHE
		if (state->have_mark)
			state->ypos = state->mark_ypos;
		state->have_mark = 0;
		break;

	case CTL_FORE:
		fore_color[caching] = cp->ctl_value;
		XSetForeground(display, gcfore, fore_color[caching]);
		break;

	case CTL_BACK:
		back_color[caching] = cp->ctl_value;
		bg_ctl = cp;	/*update later*/
		break;

	case CTL_CCOLOR:
		ctrl_color[caching] = cp->ctl_value;
		break;

	case CTL_CENTER:
		state->align = AL_CENTER;
		break;

	case CTL_LEFT:
		state->align = AL_LEFT;
		break;

	case CTL_LEFTFILL:
		state->align = AL_LEFTFILL0;
		break;

	case CTL_RIGHT:
		state->align = AL_RIGHT;
		break;

	case CTL_CONT:
		break;

#ifdef VFLIB
	case CTL_VFONT:
		vfc_setfont(cp->ctc_value);
		break;
#endif /*VFLIB*/

#ifdef FREETYPE
	case CTL_TFONT:
		tfc_setfont(cp->ctc_value, 0);
		break;

	case CTL_TMFONT:
		tfc_setfont(cp->ctc_value, 1);
		break;

#endif /*FREETYPE*/

	case CTL_XFONT2:
		x_registerseed(state, cp->ctc2_value1, cp->ctc2_value2);
		break;

	case CTL_BAR:
		draw_bar(state, cp);
		break;

	case CTL_IMAGE:
	    {
		if (state->align == AL_LEFTFILL0) {
			state->align = AL_LEFTFILL1;
			state->leftfillpos = state->linewidth;
		}

		/* quickhack for postscript */
		if (ispsfilename(cp->ctm_fname)) {
			image_load_ps(state, cp->ctm_fname, cp->ctm_numcolor,
				cp->ctm_ximagesize, cp->ctm_yimagesize, 0,
				cp->ctm_zoomflag, 0);
		} else {
			image_load(state, cp->ctm_fname, cp->ctm_numcolor,
				cp->ctm_ximagesize, cp->ctm_yimagesize, 0,
				cp->ctm_zoomflag, 0);
		}
	    }
		break;

	case CTL_BIMAGE:
		if (mgp_flag & FL_BIMAGE)
			break;
		bg_ctl = cp;	/*update later*/
		break;

	case CTL_BGRAD:
		if (mgp_flag & FL_BIMAGE)
			break;
		bg_ctl = cp;	/*update later*/
		break;

	case CTL_LCUTIN:
		CHECK_CACHE
		state->special = SP_LCUTIN;
		break;

	case CTL_RCUTIN:
		CHECK_CACHE
		state->special = SP_RCUTIN;
		break;

	case CTL_SHRINK:
		CHECK_CACHE
		state->special = SP_SHRINK;
		break;

	case CTL_PREFIX:
		state->curprefix = cp->ctc_value;
		break;

	case CTL_TABPREFIX:
		state->tabprefix = cp->ctc_value;
		break;

	case CTL_PREFIXPOS:
	    {
		char *p;

		p = (state->tabprefix) ? state->tabprefix : state->curprefix;
		if (!p)
			break;
		draw_line_output(state, p);
		break;
	    }

	case CTL_TEXT:
		if (!cp->ctc_value)
			break;
		if (state->align == AL_LEFTFILL0) {
			state->align = AL_LEFTFILL1;
			state->leftfillpos = state->linewidth;
		}
		draw_line_output(state, cp->ctc_value);
		break;

	case CTL_LINESTART:
		if (state->line == 0) {
			/*
			 * set background of target 
			 */
			if (bg_ctl) {
				if (!caching){
					/* target is window, so we need care bg_ctl_last */
					if (bg_ctl_last && !ctlcmp(bg_ctl, bg_ctl_last)){
						/* same as last time, we do nothing  */
						;
					} else {
						/* we have to change background */
						get_background_pixmap(bg_ctl, state);

						/* set window background */
						set_background_pixmap(bg_ctl);

						bg_ctl_last = bg_ctl;
					}
					XClearWindow(display, state->target);
				} else {
					get_background_pixmap(bg_ctl, state);
					bg_ctl_cache = bg_ctl;

					XClearPixmap(display, state->target);
				}
			} else {
				if (!caching)
					XClearWindow(display, state->target);
				else
					XClearPixmap(display, state->target);
			}

			if (t_fin)
				timebar(state);
		}
		draw_line_start(state);
		break;

	case CTL_LINEEND:
		/* blank lines */
		if (state->maxascent + state->maxdescent < 3) {	/*XXX*/
			state->maxascent = char_size[caching];
			state->maxdescent = VERT_GAP(char_size[caching]);
		}
		draw_line_end(state);
		/* reset single-line oriented state */
		state->tabprefix = NULL;
		state->special = 0;
		if (state->align == AL_LEFTFILL1) {
			state->align = AL_LEFTFILL0;
			state->leftfillpos = 0;
		}
		break;

	case CTL_MARK:
		state->have_mark = 1;
		state->mark_ypos = state->ypos;
		break;

	case CTL_SYSTEM:
		CHECK_CACHE
		process_system(state, cp);
		break;

	case CTL_XSYSTEM:
		CHECK_CACHE
		process_xsystem(state, cp);
		break;

	case CTL_ICON:
		process_icon(state, cp);
		break;

#ifdef VFLIB
	case CTL_VFCAP:
		vfcap_name = cp->ctc_value;
		break;
#endif

#ifdef FREETYPE
	case CTL_TFDIR:
		freetypefontdir = cp->ctc_value;
		break;

	case CTL_TFONT0:
		freetypefont0 = cp->ctc_value;
		break;

	case CTL_TMFONT0:
		freetypemfont0 = cp->ctc_value;
		break;
#endif

	case CTL_NOOP:
	case CTL_NODEF:
		break;

	case CTL_XFONT:
		/* obsolete directives */
		fprintf(stderr, "internal error: obsolete directive "
			"\"%s\"\n", ctl_words[cp->ct_op].ctl_string);
		exit(1);
		/*NOTREACHED*/
	
	case CTL_PCACHE:
		if (!caching) { 
			if (cp->ctch_flag)
				mgp_flag |= FL_FRDCACHE;
			else 
				mgp_flag ^= FL_FRDCACHE;
			cache_mode   = cp->ctch_mode;	
			cache_effect = cp->ctch_effect;	
			cache_value  = cp->ctch_value;
		} else {
			pcache.flag = 1;
			pcache.page = state->page;
			pcache.mgpflag = cp->ctch_flag;
			pcache.mode = cp->ctch_mode;
			pcache.effect = cp->ctch_effect;
			pcache.value = cp->ctch_value;
		}
		break;

	default:
		fprintf(stderr,
			"undefined directive %d at page %d line %d:\n\t",
			cp->ct_op, state->page, state->line);
		debug0(cp);
		break;
	}
}

/*
 * line management.
 */
static int
set_position(state)
	struct render_state *state;
{
	int x;

	x = 0;
	switch (state->align) {
	case AL_CENTER:
		x = (state->width - state->linewidth)/ 2;
		break;

	case AL_LEFT:
	case AL_LEFTFILL0:
	case AL_LEFTFILL1:
		x = 0;
		break;

	case AL_RIGHT:
		x = state->width - state->linewidth;
		break;
	}

	return x;
}

static void
draw_line_start(state)
	struct render_state *state;
{
	struct render_object *obj;

	state->maxascent = 0;
	state->maxdescent = 0;
	state->linewidth = 0;
	while ((obj = state->obj))
		obj_free(state, obj);
}

static void
draw_line_itemsize(state, ascent, descent)
	struct render_state *state;
	int ascent;
	int descent;
{
	if (ascent > state->maxascent)
		state->maxascent = ascent;
	if (descent > state->maxdescent)
		state->maxdescent = descent;
}


static void
draw_line_output(state, data)
	struct render_state *state;
	char *data;
{
	draw_string(state, data);
}

static void
draw_line_end(state)
	struct render_state *state;
{
	int xpos;

	xpos = set_position(state);

	/* process the special attribute. */
	switch (state->special) {
#if 0
	case SP_SHRINK:
		shrink(data, page, xpos);
		break;
#endif
	case SP_LCUTIN:
		cutin(state, xpos, state->ypos, 1);
		break;
	case SP_RCUTIN:
		cutin(state, xpos, state->ypos, -1);
		break;
	default:
		break;
	}
	if (state->obj) {
		obj_draw(state, state->target, xpos, state->ypos);
		while (state->obj)
			obj_free(state, state->obj);
	}

	state->ypos += state->maxascent;
	if (VERT_GAP(state->maxascent) < state->maxdescent)
		state->ypos += state->maxdescent;
	else
		state->ypos += VERT_GAP(state->maxascent);
	state->ypos += 2;
}

static void
cutin(state, lx, ly, dir)
	struct render_state *state;
	int lx;
	int ly;
	int dir;
{
	u_int step, x;
	int i;
	int sx;
	int round;
	Window cutinWin;

	if (state->repaint)
		return;

	if (!state->linewidth)
		return;

	sx = (0 < dir) ? 0 : state->width - state->linewidth;
	round = 20;	/*XXX*/
#ifndef abs
#define abs(a)	(((a) < 0) ? -(a) : (a))
#endif
	if (abs(lx - sx) < round){
		round = abs(lx - sx);
		if (!round) round = 1;
	}

	step = (lx - sx) / round;

	cutinWin = XCreateSimpleWindow(display, state->target,
		sx, ly, state->linewidth, state->maxascent + state->maxdescent,
		0, fore_color[caching], back_color[caching]);
	XSetWindowBackgroundPixmap(display, cutinWin, None);
	XMapSubwindows(display, state->target);

	if (state->obj) {
		obj_draw(state, cutinWin, 0, 0);
	}
	XFlush(display);

	x = sx;
	for (i = 0; i < round; i++) {
		XMoveWindow(display, cutinWin, x, ly);
		XFlush(display);
		usleep(CUTIN_DELAY);
		x += step;
	}

	XDestroyWindow(display, cutinWin);
}

#if 0
static void
shrink(data, page)
	char *data;
	u_int page;
{
	u_int min_csize = char_size;
	u_int max_csize = state->height / 4;
	u_int csize, i, x;
	u_int step = (max_csize - min_csize) / 3;

	if (!step)
		step = 1;

	if (state->align != AL_CENTER) {
		fprintf(stderr, "align is not center: \n");
		return;
	}

	csize = char_size;
	for (i = max_csize; i > min_csize; i -= step) {
		char_size = i;
		draw_string(state, data);
		x = (state->width - state->linewidth) / 2;
		XCopyArea(display, maskpix, state->target, gc,
			0, 0, state->linewidth, char_size, x, state->ypos);
		XCopyArea(display, pixmap, state->target, gcor,
			0, 0, state->linewidth, char_size, x, state->ypos);
		XFlush(display);
		usleep(SHRINK_DELAY);
		XFillRectangle(display, pixmap, gcall,
			0, 0, state->width, char_size);
		XFillRectangle(display, maskpix, gcall,
			0, 0, state->width, char_size);
		XClearArea(display, state->target, x, state->ypos,
			state->linewidth, char_size, 0);
	}
	char_size = csize;
}
#endif

/*
 * render characters.
 */
static void
draw_string(state, data)
	struct render_state *state;
	char *data;
{
	u_char *p, *q;
	char *registry = NULL;
	u_int code2;
	static char *rtab96[] = {
		NULL,			/* ESC - @ */
		"iso8859-1",		/* ESC - A */
		"iso8859-2",		/* ESC - B */
		"iso8859-3",		/* ESC - C */
		"iso8859-4",		/* ESC - D */
		NULL,			/* ESC - E */
		NULL,			/* ESC - F */
		NULL,			/* ESC - G */
		NULL,			/* ESC - H */
		NULL,			/* ESC - I */
		NULL,			/* ESC - J */
		"iso8859-11",		/* ESC - K */
		NULL,			/* ESC - L */
		NULL,			/* ESC - M */
		NULL,			/* ESC - N */
		NULL,			/* ESC - O */
		"tis620-2",		/* ESC - P */
	};
#define RTAB96_MAX	(sizeof(rtab96)/sizeof(rtab96[0]))
	static char *rtab9494[] = {
		"jisx0208.1983-*",	/* ESC $ @ or ESC $ ( @ */
		"gb2312.1980-*",	/* ESC $ A or ESC $ ( A */
		"jisx0208.1983-*",	/* ESC $ B or ESC $ ( B */
		"ksc5601.1987-*",	/* ESC $ ( C */
	};
#define RTAB9494_MAX	(sizeof(rtab9494)/sizeof(rtab9494[0]))
	int charset16 = 0;

	p = (u_char *)data;

	while (*p && *p != '\n') {
		/* 94x94 charset */
		if (p[0] == 0x1b && p[1] == '$'
		 && '@' <= p[2] && p[2] < 'C' && rtab9494[p[2] - '@']) {
			registry = rtab9494[p[2] - '@'];
			charset16 = 1;
			p += 3;
			continue;
		}
		if (p[0] == 0x1b && p[1] == '$' && p[2] == '('
		 && '@' <= p[3] && p[3] < '@' + RTAB9494_MAX
		 && rtab9494[p[3] - '@']) {
			registry = rtab9494[p[3] - '@'];
			charset16 = 1;
			p += 4;
			continue;
		}
		/* ascii (or JIS roman) */
		if (p[0] == 0x1b && p[1] == '('
		 && (p[2] == 'B' || p[2] == 'J')) {
			registry = NULL;
			charset16 = 0;
			p += 3;
			continue;
		}
		/* 96 charset */
		if (p[0] == 0x1b && p[1] == '-'
		 && '@' < p[2] && p[2] < '@' + RTAB96_MAX
		 && rtab96[p[2] - '@']) {
			registry = rtab96[p[2] - '@'];
			charset16 = 0;
			p += 3;
			continue;
		}

		if (!registry && isspace(p[0])) {
			draw_fragment(state, p, 1, registry, 0);
			p++;
			continue;
		}

		if (charset16) {
			for (q = p + 2; 0x21 <= *q && *q <= 0x7e; q += 2) {
				code2 = q[0] * 256 + q[1];
				if (strncmp(registry, "jisx0208", 8) == 0
				 && !iskinsokuchar(code2)) {
					break;
				}
			}
		} else {
			q = p;
#ifdef THAI
/*
 * 2000-07-19  Theppitak:
 * Don't use isprint() so Thai string is drawn as a chunk for nicer spacing.
 * Note: This could be removed when Thai LC_CTYPE works, perhaps, after
 * Thai XIM bug is fixed.
 */
			while (*q && !isspace(*q))
#else
/* Original code */
			while (*q && isprint(*q) && !isspace(*q))
#endif
				q++;
			if (q == p)
				q++;
			else {
				/*
				 * append spaces to the end of the word.
				 * fragments in the following line:
				 *	"this is test"
				 * are:
				 *	"this_" "is_" "test"
				 */
				while (*q && isspace(*q))
					q++;
			}
		}

		draw_fragment(state, p, q - p, registry, charset16);

		p = q;
	}
}

static void
draw_fragment(state, p, len, registry, charset16)
	struct render_state *state;
	u_char *p;
	u_int len;
	char *registry;
	int charset16;	/*2-octet charset?*/
{
	u_int char_len;
	u_short code;
	struct render_object *tail;
	struct render_object *thisline;
	struct render_object *thislineend;
	u_int startwidth;
	struct render_state backup0, backup;
	enum { MODE_UNKNOWN, MODE_X, MODE_VFLIB, MODE_FREETYPE }
		mode = MODE_UNKNOWN;

	if (state->obj)
		tail = state->objlast;
	else
		tail = NULL;
	startwidth = state->linewidth;

	while (len) {
		code = charset16 ? p[0] * 256 + p[1] : p[0];

#if 0
		if (code == ' ') {
			char_len = char_size[caching] / 2;
			p++;
			len--;

			state->linewidth += HORIZ_STEP(char_size[caching], char_len);
			continue;
		}
#endif
		if (code == '\t') {
			char_len = char_size[caching] / 2;
			p++;
			len--;

			char_len = HORIZ_STEP(char_size[caching], char_len) * 8;/*XXX*/
			state->linewidth = (state->linewidth + char_len) / char_len * char_len;
			continue;
		}

		/*
		 * decide which font to use.
		 * Japanese font:
		 *	VFlib - optional
		 *	then X.
		 * Western font:
		 *	If possible, freetype. (in the future) - optional
		 *	X if truely scalable.
		 *	VFlib if it is larger than some size - optional
		 *	otherwise, X.
		 */
		mode = MODE_UNKNOWN;
		if (charset16) {
#ifdef VFLIB
			if (!(mgp_flag & FL_NOVFLIB)
			 && strncmp(registry, "jisx0208.1983-", 14) == 0)
				mode = MODE_VFLIB;
#endif
#ifdef FREETYPE_CHARSET16
			if (!(mgp_flag & FL_NOFREETYPE)
			 && strncmp(registry, "jisx0208.1983-", 14) == 0) {
				if (tfc_get(code, char_size[caching], 1, registry,
						charset16)){
					mode = MODE_FREETYPE;
				}
			}
#endif
			if (mode == MODE_UNKNOWN)
				mode = MODE_X;
		} else {
#ifdef FREETYPE
			if (!(mgp_flag & FL_NOFREETYPE)) {
				if (tfc_get(code, char_size[caching], 1, registry,
						charset16)) {
					mode = MODE_FREETYPE;
				}
			}
#endif
			if (mode == MODE_UNKNOWN) {
				/*
				 * if we can have X font that is exactly
				 * matches the required size, we use that.
				 */
				XFontStruct *xfontstruct;
				int ts;
				xfontstruct = x_setfont(
					x_findseed(state, registry),
					char_size[caching], registry, &ts);
				if (ts)
					mode = MODE_X;
			}
#ifdef VFLIB
# ifdef USE_XDRAWSTRING_ONLY_SMALL
			if (!(mgp_flag & FL_NOVFLIB) && mode == MODE_UNKNOWN) {
				if (25 < char_size)
					mode = MODE_VFLIB;
			}
# endif /* USE_XDRAWSTRING_ONLY_SMALL */
#endif

			/* last resort: use X font. */
			if (mode == MODE_UNKNOWN)
				mode = MODE_X;
		}

		/* back it up before drawing anything */
		memcpy(&backup0, state, sizeof(struct render_state));

		switch (mode) {
#ifdef VFLIB
		case MODE_VFLIB:
			char_len = draw_onechar_vf(state, code,
				state->linewidth, 0,
				registry ? char_size[caching]
					 : (char_size[caching] * 4 / 5), /*XXX*/
				char_size[caching]);
			break;
#endif
#ifdef FREETYPE
		case MODE_FREETYPE:
			/*
			 * NOTE: width and height parameter (4th and 5th)
			 * are meaningless for FreeType, since we use
			 * metric info derived from TrueType font file.
			 */
			char_len = draw_onechar_tf(state, code,
				state->linewidth, 0,
				char_size[caching], registry,
				(len == (charset16 ? 2 : 1)) ? 1 : 0,
				charset16);
			break;
#endif
		default:
			fprintf(stderr, "invalid drawing mode %d for %04x "
				"- fallback to X11\n", mode, code);
			/* fall through */
		case MODE_UNKNOWN:
		case MODE_X:
			char_len = draw_onechar_x(state, code,
				state->linewidth, 0, char_size[caching],
				registry, (len == (charset16 ? 2 : 1)) ? 1 : 0);
			if (char_len == 0) {
				fprintf(stderr, "can't load font size %d "
					"(nor font in similar size) for "
					"font <%s:%d:%s>, glyph 0x%04x\n",
					char_size[caching], x_findseed(state, registry),
					char_size[caching], registry?registry:"NULL", code);
			}
			break;
		}

		p += (charset16 ? 2 : 1);
		len -= (charset16 ? 2 : 1);

		state->linewidth += HORIZ_STEP(char_size[caching], char_len);
	}

	if (state->width - state->leftfillpos / 2 < state->linewidth
#if 0
	 && state->align == AL_LEFTFILL1
#endif
	   ) {
		memcpy(&backup, state, sizeof(struct render_state));

		/* strip off the last fragment we wrote. */
		if (tail) {
			thisline = tail->next;
			thislineend = state->objlast;
			tail->next = NULL;
			state->objlast = tail;
			state->maxascent = backup0.maxascent;
			state->maxdescent = backup0.maxdescent;
		} else {
			thisline = state->obj;
			thislineend = state->objlast;
			state->obj = state->objlast = NULL;
			state->maxascent = backup0.maxascent;
			state->maxdescent = backup0.maxdescent;
		}
#if 0
		state->align = AL_LEFT;
#endif
		state->linewidth = startwidth;
		draw_line_end(state);	/* flush the line. */

		/* start the new line with the last fragment we wrote. */
		draw_line_start(state);
		state->linewidth = state->leftfillpos;
		state->linewidth += (backup.linewidth - startwidth);
		if (state->obj && state->objlast)
			state->objlast->next = thisline;
		else
			state->obj = thisline;
		state->objlast = thislineend;
		state->align = backup.align;

		/* fix up x position and maxascent. */
		for (tail = state->obj; tail; tail = tail->next) {
			tail->x -= startwidth;
			tail->x += state->leftfillpos;
			draw_line_itemsize(state, tail->ascent, tail->descent);
		}
	}
}

static int
iskinsokuchar(code)
	u_int code;
{
	u_short *kinsoku;

	for (kinsoku = kinsokutable; *kinsoku; kinsoku++) {
		if (code == *kinsoku)
			return 1;
	}
	return 0;
}

static struct render_object *
obj_alloc(state)
	struct render_state *state;
{
	struct render_object *obj;

	obj = malloc(sizeof(*obj));
	if (obj == NULL)
		return NULL;
	obj->next = NULL;
	if (state->obj == NULL)
		state->obj = obj;
	else
		state->objlast->next = obj;
	state->objlast = obj;
	return obj;
}

static void
obj_free(state, obj)
	struct render_state *state;
	struct render_object *obj;
{
	struct render_object *o;

	if (state->obj == obj)
		state->obj = obj->next;
	else {
		for (o = state->obj; o; o = o->next)
			if (o->next == obj)
				break;
		/* ASSERT(o != NULL); */
		o->next = obj->next;
	}
	if (state->objlast == obj)
		state->objlast = obj->next;
	switch (obj->type) {
#ifdef VFLIB
	case O_VFONT:
		obj->data.vfc->ref--;
		break;
#endif /* VFLIB */
#ifdef FREETYPE
	case O_TFONT:
		obj->data.tfc->ref--;
		break;
#endif /* FREETYPE */
	case O_IMAGE:
		freeImage(obj->data.image.image);
		break;
	case O_XFONT:
		free(obj->data.xfont.xfont);
		break;
	case O_ICON:
		if (obj->data.icon.xpoint)
			free(obj->data.icon.xpoint);
		break;
	}
	free(obj);
}

#ifdef VFLIB
static int
obj_new_vfont(state, x, y, vfc, size)
	struct render_state *state;
	int x, y;
	struct vfont *vfc;
	int size;
{
	struct render_object *obj;

	obj = obj_alloc(state);
	if (obj == NULL)
		return 0;
	obj->x = x;
	obj->y = y;
	obj->fore = fore_color[caching];
	obj->type = O_VFONT;
	obj->data.vfc = vfc;
	obj->data.vfc->size = size;
	obj->ascent = obj->data.vfc->ascent;
	obj->descent = obj->data.vfc->descent;
	obj->vertloc = VL_BASE;
	vfc->ref++;
	return 1;
}
#endif /* VFLIB */

#ifdef FREETYPE
static int
obj_new_tfont(state, x, y, tfc)
	struct render_state *state;
	int x, y;
	struct tfont *tfc;
{
	struct render_object *obj;

	obj = obj_alloc(state);
	if (obj == NULL)
		return 0;
	obj->x = x;
	obj->y = y;
	obj->fore = fore_color[caching];
	obj->type = O_TFONT;
	obj->data.tfc = tfc;
	obj->ascent = obj->data.tfc->ascent;
	obj->descent = obj->data.tfc->descent;
	obj->vertloc = VL_BASE;
	tfc->ref++;
	return 1;
}
#endif /* FREETYPE */

static int
obj_new_xfont(state, x, y, size, code, registry)
	struct render_state *state;
	int x, y;
	int size;
	u_int code;
	char *registry;
{
	struct render_object *obj;

	obj = obj_alloc(state);
	if (obj == NULL)
		return 0;
	obj->x = x;
	obj->y = y;
	obj->fore = fore_color[caching];
	obj->type = O_XFONT;
	obj->data.xfont.xfont = strdup(x_findseed(state, registry));
	obj->data.xfont.csize = size;
	obj->data.xfont.code = code;
	obj->data.xfont.registry = registry;
	obj->ascent = size;	/*XXX*/
	obj->descent = 0;	/*XXX*/
	obj->vertloc = VL_BASE;
	return 1;
}

static int
obj_new_image(state, x, y, image, xzoom, yzoom)
	struct render_state *state;
	int x, y;
	Image *image;
	int xzoom, yzoom;
{
	struct render_object *obj;

	obj = obj_alloc(state);
	if (obj == NULL)
		return 0;
	obj->x = x;
	obj->y = y;
	obj->type = O_IMAGE;
	obj->data.image.image = image;
	obj->data.image.xzoom = xzoom;
	obj->data.image.yzoom = yzoom;
	obj->ascent = 0;	/*XXX*/
	obj->descent = image->height * yzoom / 100;	/*XXX*/
	obj->vertloc = VL_TOP;
	return 1;
}

static int
obj_new_icon(state, x, y, itype, isize, color, npoint, xpoint)
	struct render_state *state;
	int x, y;
	u_int itype, isize;
	u_long color;
	u_int npoint;
	XPoint *xpoint;
{
	struct render_object *obj;
	int i;

	obj = obj_alloc(state);
	if (obj == NULL)
		return 0;
	obj->x = x;
	obj->y = y;
	obj->fore = color;
	obj->type = O_ICON;
	obj->data.icon.itype = itype;
	obj->data.icon.isize = isize;
	obj->data.icon.npoint = npoint;
	if (npoint) {
		obj->data.icon.xpoint = malloc(sizeof(XPoint) * npoint);
		if (obj->data.icon.xpoint == NULL) {
			obj_free(state, obj);
			return 0;
		}
		for (i = 0; i < npoint; i++)
			obj->data.icon.xpoint[i] = xpoint[i];
	} else
		obj->data.icon.xpoint = NULL;
	obj->ascent = 0;	/*XXX*/
	obj->descent = isize;	/*XXX*/
	obj->vertloc = VL_CENTER;
	return 1;
}

static Pixel
obj_image_color(image, bimage, d, inithist)
	Image *image, *bimage;
	Pixel d;
	int *inithist;
{
	int i, j;
	RGBMap rgb;
	int r, g, b;
	static char hist[256];
	byte *p;

	switch (bimage->type) {
	case IBITMAP:
		r = g = b = d ? 0xffff : 0;
		break;
	case IRGB:
		r = bimage->rgb.red[d];
		g = bimage->rgb.green[d];
		b = bimage->rgb.blue[d];
		break;
	case ITRUE:
		r = TRUE_RED(d) << 8;
		g = TRUE_GREEN(d) << 8;
		b = TRUE_BLUE(d) << 8;
		break;
	default:
		return 0;
	}
	if (image->type == ITRUE)
		return RGB_TO_TRUE(r, g, b);

	for (i = 0; i < image->rgb.used; i++) {
		if (image->rgb.red[i] == r &&
		    image->rgb.green[i] == g &&
		    image->rgb.blue[i] == b)
			return i;
	}
	if (i >= image->rgb.size) {
		if (i >= 256) {
			/* search a free slot */
			if (image->rgb.size == 256) {
				if (!*inithist) {
					*inithist = 1;
					memset(hist, 0, sizeof(hist));
					p = image->data;
					for (j = 0; j < image->height; j++)
						for (i = 0; i < image->width; i++)
							hist[*p++] = 1;
				}
				for (i = 0; i < 256; i++) {
					if (hist[i] == 0) {
						hist[i] = 1;
						goto freeslot;
					}
				}
			}
			return -1;
		}
		image->depth = 8;
		newRGBMapData(&rgb, depthToColors(image->depth));
		for (i = 0; i < image->rgb.used; i++) {
			rgb.red[i] = image->rgb.red[i];
			rgb.green[i] = image->rgb.green[i];
			rgb.blue[i] = image->rgb.blue[i];
		}
		rgb.used = i;
		freeRGBMapData(&image->rgb);
		image->rgb = rgb;
	}
  freeslot:
	image->rgb.red[i] = r;
	image->rgb.green[i] = g;
	image->rgb.blue[i] = b;
	if (image->rgb.used < i + 1)
		image->rgb.used = i + 1;
	return i;
}

static Image *
obj_image_trans(image, x, y)
	Image *image;
	u_int x, y;
{
	Image *timage;
	int i, j;
	byte *p, *b;
	Pixel d, n, pd;
	static XColor xcol;
	int pl, bpl;
	int trans;
	u_int bw, bh, bx, by;
	int inithist;

	if (!COMPLEX_BGIMAGE) {
		if (back_color[caching] != xcol.pixel) {
			xcol.pixel = back_color[caching];
			xcol.flags = DoRed|DoGreen|DoBlue;
			XQueryColor(display, colormap, &xcol);
		}
		switch (image->type) {
		case IBITMAP:
		case IRGB:
			image->rgb.red[image->trans] = xcol.red;
			image->rgb.green[image->trans] = xcol.green;
			image->rgb.blue[image->trans] = xcol.blue;
			break;
		case ITRUE:
			d = image->trans;
			n = RGB_TO_TRUE(xcol.red, xcol.green, xcol.blue);
			pl = image->pixlen;
			p = image->data;
			for (j = 0; j < image->height; j++) {
				for (i = 0; i < image->width; i++, p += pl) {
					if (memToVal(p, pl) == d)
						valToMem(n, p, pl);
				}
			}
			break;
		}
		bw = bh = 0;	/* for lint */
		goto end;
	}
	bh = bgpixmap[bgindex].image->height;
	bw = bgpixmap[bgindex].image->width;
	j = 0;
	if (image->type == IBITMAP) {
  expand:
		timage = image;
		if (verbose)
			fprintf(stderr, "obj_image_trans: expanding image\n");
		image = expand(image);
		if (image != timage)
			freeImage(timage);
	}
	pl = image->pixlen;
	p = image->data + image->width * j * pl;
	bpl = bgpixmap[bgindex].image->pixlen;
	pd = -1;
	n = 0;	/* for lint */
	trans = image->trans;
	inithist = 0;
	for ( ; j < image->height; j++) {
		by = (y + j) % bh;
		bx = x % bw;
		b = bgpixmap[bgindex].image->data + 
			(bgpixmap[bgindex].image->width * by + bx) * bpl;
		for (i = 0; i < image->width; i++, p += pl, b += bpl, bx++) {
			if (bx == bw) {
				bx = 0;
				b = bgpixmap[bgindex].image->data + 
					bgpixmap[bgindex].image->width * by * bpl;
			}
			if (memToVal(p, pl) != trans)
				continue;
			d = memToVal(b, bpl);
			if (d != pd) {
				pd = d;
				n = obj_image_color(image, 
						bgpixmap[bgindex].image, d, &inithist);
				if (n == -1)
					goto expand;
			}
			valToMem(n, p, pl);
		}
	}
  end:
	if (verbose) {
		char *p;

		switch (image->type) {
		case IBITMAP:	p = "bitmap"; break;
		case IRGB:	p = "rgb"; break;
		default:	p = "true"; break;
		}
		fprintf(stderr, "obj_image_trans: %s: "
			"trans=%d, rgb_used=%d, rgb_size=%d\n",
			p, image->trans, image->rgb.used, image->rgb.size);
		fprintf(stderr, "  image=%dx%d+%d+%d",
			image->width, image->height, x, y);
		if (COMPLEX_BGIMAGE)
			fprintf(stderr, "  bgpixmap[bgindex].image=%dx%d", bw, bh);
		fprintf(stderr, "\n");
	}
	image->trans = -1;	/* XXX: need recalculation to redraw? */
	return image;
}

static void
obj_draw_image(target, x, y, obj)
	Drawable target;
	u_int x, y;
	struct render_object *obj;
{
	Image *image, *timage;
	XImageInfo *ximageinfo;
	XImage *xim;
	int private = mgp_flag & FL_PRIVATE;

	image = obj->data.image.image;
	if (obj->data.image.xzoom != 100.0 || obj->data.image.yzoom != 100.0) {
		timage = image;
		image = zoom(image,
			obj->data.image.xzoom, obj->data.image.yzoom, verbose);
		if (!image) {
			fprintf(stderr, "image zoom (%0.2fx%0.2f) failed in obj_draw_image\n",
				obj->data.image.xzoom, obj->data.image.yzoom);
			exit(1);
		}
		freeImage(timage);
	}
	if (image->trans >= 0)
		image = obj_image_trans(image, x, y);
	obj->data.image.image = image;	/* to free later */
	ximageinfo= imageToXImage(display, screen, visual, depth, image,
				private, 0,0, verbose);
	if (ximageinfo == NULL) {
		fprintf(stderr, "Cannot convert Image to XImage\n");
		cleanup(-1);
	}
	xim = ximageinfo->ximage;
	if (xim->format == XYBitmap)
		XSetBackground(display, gcfore, back_color[caching]);
	XPutImage(display, target, gcfore, xim, 0, 0,
		x, y, xim->width, xim->height);
	freeXImage(image, ximageinfo);
}

static void
obj_draw(state, target, xpos, ypos)
	struct render_state *state;
	Drawable target;
	u_int xpos, ypos;
{
	struct render_object *obj;
	int x = 0, y = 0;
	int width, height, xwidth, xheight;
	u_long fore;
	u_int code;
	char *registry;
	XChar2b kch[2];
#define MAXDRAWAREA 1024
	struct {
		int x, y, width, height;
	} drawarea[MAXDRAWAREA];
	int areaindex = 0;
#define addarea(X) \
{\
	if (areaindex == MAXDRAWAREA){\
		fprintf(stderr, "too many drawarea (increase MAXDRAWAREA)\n");\
			exit(1);\
	}\
	drawarea[areaindex].x = x;\
	drawarea[areaindex].y = y - obj->data.X->ascent;\
	drawarea[areaindex].width = obj->data.X->charlen+1;\
	drawarea[areaindex].height = obj->data.X->height+1;\
	areaindex ++;\
}
#if 0
	char ch[2];
#endif
	u_int isize;
	int i;
#ifdef RASTERLIB
	XImage *bim, *xim;
	u_long bcolor;
#endif /* RASTERLIB */

	/*
	 * very complicated...
	 *
	 *	xpos, ypos	x/y position of the target,
	 *			leftmost and uppermost dot.
	 *	state->ypos	absolute y position in main window.
	 */
	width = (state->linewidth <= state->width - xpos)
			? state->linewidth
			: state->width - xpos;
	height = state->maxascent + state->maxdescent + 1;
	fore = fore_color[caching];

#ifdef RASTERLIB
	bcolor = back_color[caching];
	for (obj = state->obj; obj; obj = obj->next) {
#ifdef VFLIB
		if (obj->type == O_VFONT){
			xwidth = obj->data.vfc->width;
			xheight = obj->data.vfc->height;
			break;
		}
#endif /* VFLIB */
#ifdef FREETYPE
		if (obj->type == O_TFONT){
			xwidth = obj->data.tfc->width;
			xheight = obj->data.tfc->height;
			break;
		}
#endif /* FREETYPE */
	}
	if (obj != NULL) {	/* VFONT exist */
		xim = XCreateImage(display, visual, depth, ZPixmap,
				0, NULL, width, height,
				8 << (depth - 1) / 8, 0);
		xim->data = malloc(xim->bytes_per_line * height);
		if (COMPLEX_BGIMAGE) {
			u_int bw, bh, bx, by, ox, oy;
			u_long p;
			u_long r, g, b;
			byte *bp;
			int bpl;
			XColor col;

			bim = bgpixmap[bgindex].ximageinfo->ximage;
			bw = bim->width;
			bh = bim->height;
			ox = xpos;
			oy = state->ypos;
			bcolor = (u_long)-1; /* tell vfc_image() to calculate */
			by = oy % bh;
			if (bw == 1) {
				r = g = b = 0;
				bpl = bgpixmap[bgindex].image->pixlen;
				bp = bgpixmap[bgindex].image->data + by * bpl;
				for (y = 0;
				     y < height;
				     y++, by++, bp += bpl) {
					if (by == bh)
						by = 0;
					p = memToVal(bp, bpl);
					if (TRUEP(bgpixmap[bgindex].image)) {
						r += TRUE_RED(p) << 8;
						g += TRUE_GREEN(p) << 8;
						b += TRUE_BLUE(p) << 8;
					} else {
						r += bgpixmap[bgindex].image->rgb.red[p];
						g += bgpixmap[bgindex].image->rgb.green[p];
						b += bgpixmap[bgindex].image->rgb.blue[p];
					}
					p = XGetPixel(bim, 0, by);
					for (x = 0; x < width; x++)
						XPutPixel(xim, x, y, p);
				}
				col.red = r / height;
				col.green = g / height;
				col.blue = b / height;
				col.flags = DoRed|DoGreen|DoBlue;
				/* XXX:actually we don't need to allocate. */
				if (XAllocColor(display, colormap, &col)) {
					regist_alloc_colors(&font_clr,
						&col.pixel, 1);
					bcolor = col.pixel;
				}
#if 0
			fprintf(stderr, "bim=%dx%d, r=%x, g=%x, b=%x, "
				"bcolor=%x\n",
				bgpixmap[bgindex].image->width, bgpixmap[bgindex].image->height,
				col.red, col.green, col.blue, bcolor);
#endif
			} else {
				for (y = 0; y < height; y++, by++) {
					if (by == bh)
						by = 0;
					for (x = 0, bx = ox % bw; x < width; x++, bx++) {
						if (bx == bw)
							bx = 0;
						p = XGetPixel(bim, bx, by);
						XPutPixel(xim, x, y, p);
					}
				}
			}
		} else {
			memset(xim->data, 0, xim->bytes_per_line * height);
			XAddPixel(xim, bcolor);
		}
		for ( ; obj; obj = obj->next) {
			x = obj->x;
			switch (obj->vertloc) {
			case VL_BASE:
				y = state->maxascent;
				break;
			case VL_CENTER:
				y = (state->maxascent + state->maxdescent) / 2;
				y += (obj->ascent - obj->descent) / 2;
				break;
			case VL_TOP:
				y = obj->ascent;
				break;
			case VL_BOTTOM:
				y = state->maxascent + state->maxdescent;
				y -= obj->descent;
				break;
			}
#ifdef VFLIB
			if (obj->type == O_VFONT) {
				(void)vfc_image(obj->data.vfc,
					obj->fore, bcolor, xim, x, y);
				addarea(vfc);
			}
#endif /* VFLIB */
#ifdef FREETYPE
			if (obj->type == O_TFONT) {
				(void)tfc_image(obj->data.tfc,
					obj->fore, bcolor, xim, x, y);
				addarea(tfc);
			}
#endif /* FREETYPE */
		}
#if 0
		XPutImage(display, target, gcfore, xim, 0, 0,
			xpos, ypos, width, height);
#else
		for (i = 0; i < areaindex; i ++)
			XPutImage(display, target, gcfore, xim,
				drawarea[i].x, drawarea[i].y,
				drawarea[i].x + xpos, ypos + drawarea[i].y,
				drawarea[i].width, drawarea[i].height);
#endif
		XDestroyImage(xim);
		if (mgp_flag & FL_GLYPHEDGE) {
			XDrawLine(display, target, gcfore, 0, ypos,
				state->width - 1, ypos);
			XDrawLine(display, target, gcfore,
				0, ypos + state->maxascent,
				state->width - 1, ypos + state->maxascent);
			XDrawLine(display, target, gcgreen,
				0, ypos + state->maxascent + state->maxdescent,
				state->width - 1,
				ypos + state->maxascent + state->maxdescent);
			XDrawLine(display, target, gcred,
				0, ypos + height,
				state->width - 1, ypos + height);
		}

	}
#endif /* RASTERLIB */
	for (obj = state->obj; obj; obj = obj->next) {
#if 0
		x = obj->x + offx;
		y = obj->y + offy;
#else
		x = obj->x;
		switch (obj->vertloc) {
		case VL_BASE:
			y = state->maxascent;
			break;
		case VL_CENTER:
			y = (state->maxascent + state->maxdescent) / 2;
			y += (obj->ascent - obj->descent) / 2;
			break;
		case VL_TOP:
			y = obj->ascent;
			break;
		case VL_BOTTOM:
			y = state->maxascent + state->maxdescent;
			y -= obj->descent;
			break;
		}
		x += xpos;
		y += ypos;
#endif
		switch (obj->type) {
		case O_IMAGE:
			obj_draw_image(target, x, y, obj);
			break;
		case O_XFONT:
			code = obj->data.xfont.code;
			registry = obj->data.xfont.registry;
			(void)x_setfont(obj->data.xfont.xfont,
				obj->data.xfont.csize,
				registry, NULL);
			if (obj->fore != fore) {
				fore = obj->fore;
				XSetForeground(display, gcfore, fore);
			}

#if 1
			/* is it always okay? */
			kch[0].byte1 = (code >> 8) & 0xff;
			kch[0].byte2 = code & 0xff;
			XDrawString16(display, target, gcfore, x, y, kch, 1);
#else
			if (registry) {
				kch[0].byte1 = (code >> 8) & 0xff;
				kch[0].byte2 = code & 0xff;
				XDrawString16(display, target, gcfore,
					x, y, kch, 1);
			} else {
				ch[0] = code & 0xff;
				XDrawString(display, target, gcfore,
					x, y, ch, 1);
			}
#endif
			break;
		case O_ICON:
			if (obj->fore != fore) {
				fore = obj->fore;
				XSetForeground(display, gcfore, fore);
			}
			isize = obj->data.icon.isize;
			switch (obj->data.icon.itype) {
			case 1: /* this is box */
				XFillRectangle(display, target, gcfore, x, y,
					isize, isize);
				break;
			case 2: /* this is arc */
				XFillArc(display, target, gcfore, x, y,
					isize, isize, 0, 360 * 64);
				break;
			case 3: case 4: case 5: case 6:
			case 7:
				for (i = 0; i < obj->data.icon.npoint; i++) {
					obj->data.icon.xpoint[i].x += x;
					obj->data.icon.xpoint[i].y += y;
				}
				XFillPolygon(display, target, gcfore, 
					obj->data.icon.xpoint,
					obj->data.icon.npoint,
					Convex, CoordModeOrigin);
				break;
			}
			break;
		default:
			break;
		}
	}
	if (fore != fore_color[caching])
		XSetForeground(display, gcfore, fore_color[caching]);
	/* ASSERT(state->obj == NULL); */
	/* ASSERT(state->objlast == NULL); */
}

#ifdef VFLIB
static u_int
draw_onechar_vf(state, code, x, y, width, height)
	struct render_state *state;
	u_int code;
	int x, y;
	u_int width, height;
{
	struct vfont *vfc;

	vfc = vfc_get(code, width, height, 1);
	draw_line_itemsize(state, vfc->ascent, vfc->descent);

	obj_new_vfont(state, x, y, vfc, height);
	return vfc->charlen;
}
#endif /* VFLIB */

static char *
x_fontname(buf, bufsiz, seed, siz, registry)
	char *buf;
	int bufsiz;
	char *seed;
	int siz;
	char *registry;	/* already canonicalized */
{
	int hyphen;
	char *p;
	char tmp[BUFSIZ];
	char tmp2[BUFSIZ];
	char **fontlist;
	int count;

	if (!registry)
		registry = "iso8859-1";

	if (siz < 0)
		strcpy(tmp2, "*");
	else
		sprintf(tmp2, "%d", siz);

	hyphen = 0;
	for (p = seed; *p; p++) {
		if (*p == '-')
			hyphen++;
	}
	switch (hyphen) {
	case 0:
		/* for "a14", "5x8", or such an short names */
		if ((fontlist = XListFonts(display, seed, 1, &count))) {
			XFreeFontNames(fontlist);
			strcpy(buf, seed);
			break;
		}
		sprintf(tmp, "%s-*-*", seed);
		sprintf(buf, FONT_FORMAT, tmp, tmp2, registry);
		break;
	case 2:
		sprintf(buf, FONT_FORMAT, seed, tmp2, registry);
		break;
	case XLFD_HYPHEN:
		/* as is */
		strcpy(buf, seed);
		break;
	case 1:	/* should not happen */
		fprintf(stderr, "internal error: invalid seed <%s>\n", seed);
		exit(1);
	}
	if (mgp_flag & FL_VERBOSE) {
		fprintf(stderr, "fontname: seed=<%s> siz=<%d> reg=<%s> "
			"result=<%s>\n",
			seed, siz, registry, buf);
	}
	return buf;
}

static int
x_parsefont(xfont, pixel, truescalable)
	char *xfont;
	int *pixel;
	int *truescalable;
{
	char *p;
	int fsize;
	int i;

	/* go toward pixel size */
	p = xfont;
	for (i = 0; *p && i < 7; i++) {
		/* go toward minus sign */
		while (*p && *p != '-')
			p++;
		/* skip minus sign */
		if (*p)
			p++;
	}

	if (!*p)
		return -1;
	fsize = atoi(p);
	if (pixel)
		*pixel = fsize;

	/* skip pixel size */
	while (*p && (isdigit(*p) || *p == '*'))
		p++;
	if (*p == '-')
		p++;
	else
		return -1;

	/* skip point size */
	while (*p && (isdigit(*p) || *p == '*'))
		p++;
	if (*p == '-')
		p++;
	else
		return -1;

	if (truescalable) {
		if (fsize == 0 && (p[0] == '0' || p[0] == '*') && p[1] == '-')
			*truescalable = 1;
		else
			*truescalable = 0;
	}
	return 0;
}

static XFontStruct *
x_setfont(xfont, csize, registry, truescalable)
	char *xfont;
	u_int csize;
	char *registry;
	int *truescalable;
{
	static XFontStruct *xfontstruct;
	int i, fsize;
	char fontstring[BUFSIZ];
#define	FONTTYPEMAX	10	/* number of used fontlist type (in cache) */
#define	FONTLISTMAX	20	/* number of list for specified font type */
#define	FONTALLOWMAX	105	/* % of desired font */
#define	FONTALLOWMIN	90	/* % of desired font */
	char **fontlist, **font;
	u_int error;
	int best, freeindex, count;
	int maxsize, minsize;
	int scalable, tscalable, tsflag;
	static struct {
		char *xlfd;
		char **list;
		int count;
	} fontnames[FONTTYPEMAX];
#define	FONTCACHEMAX	200	/* number of used font type (in cache) */
	static struct {
		char *xfont;
		u_int csize;
		char *registry;
		char *xlfd;
		XFontStruct *xfontstruct;
	} fonts[FONTCACHEMAX];

	/*
	 * Check font cache first.
	 */
	for (i = 0; i < FONTCACHEMAX; i++) {
		if (!fonts[i].xfontstruct)
			continue;
		if (fonts[i].csize != csize || fonts[i].registry != registry
		 || strcmp(fonts[i].xfont, xfont) != 0) {
			continue;
		}

#if 0
		if (verbose) {
			fprintf(stderr, "font cache hit: entry %d <%s>\n",
				i, fonts[i].xlfd);
		}
#endif

		XSetFont(display, gcfore, fonts[i].xfontstruct->fid);
		return fonts[i].xfontstruct;
	}

	/*
	 * load new font.
	 */
	if (csize < 5) {
		xfontstruct = XLoadQueryFont(display, "nil2");
		goto gotfont;
	}

	if (verbose) {
		fprintf(stderr, "need font <%s:%d:%s>\n",
			xfont, csize, registry?registry:"NULL");
	}

	/*
	 * Look for the best font possible.
	 * 1. Check for a font that is smaller than the required one.
	 *    By using smaller font, we won't make the screen garbled.
	 * 2. If 1. is impossible, look for slightly larger font than
	 *    the required one.
	 */
	fontlist = NULL;
	freeindex = -1;
	x_fontname(fontstring, sizeof(fontstring), xfont, -1, registry);
	if (verbose)
		fprintf(stderr, "fontstring <%s>\n", fontstring);
	for (i = 0; i < FONTTYPEMAX; i++) {
		if (fontnames[i].xlfd == NULL) {
			if (freeindex < 0)
				freeindex = i;
			continue;
		}
		if (strcmp(fontnames[i].xlfd, fontstring) == 0) {
			fontlist = fontnames[i].list;
			count = fontnames[i].count;
			freeindex = i;
			break;
		}
	}
	if (fontlist == NULL) {
		fontlist = XListFonts(display, fontstring, FONTLISTMAX, &count);
		if (fontlist == NULL)
			return NULL;
		if (freeindex >= 0) {
			if (fontnames[freeindex].xlfd)
				free(fontnames[freeindex].xlfd);
			fontnames[freeindex].xlfd = strdup(fontstring);
			fontnames[freeindex].list = fontlist;
			fontnames[freeindex].count = count;
		}
	}
	error = (u_int)-1;
	best = -1;
	maxsize = csize * FONTALLOWMAX / 100;		/* truncate */
	minsize = (csize * FONTALLOWMIN + 99) / 100;	/* roundup */
	if (verbose)
		fprintf(stderr, "checking %d to %d\n", minsize, maxsize);
	scalable = tscalable = -1;
	if (truescalable)
		*truescalable = 0;
	for (i = 0, font = fontlist; i < count; i++, font++) {
		if (x_parsefont(*font, &fsize, &tsflag) < 0) {
#if 1
			if (verbose) {
				fprintf(stderr, " [%d] <%s>: nosize\n",
					i, *font);
			}
#endif
			continue;
		}
		if (fsize == 0) {
			if (scalable < 0)
				scalable = i;
			if (tsflag) {
				tscalable = i;
				if (truescalable)
					*truescalable = 1;
			}
			if (verbose) {
				fprintf(stderr, " [%d] <%s>: scalable (%d)\n",
					i, *font, tsflag);
			}
			continue;
		} else if (fsize > maxsize || fsize < minsize) {
#if 0
			if (verbose) {
				fprintf(stderr, " [%d] <%s>: reject\n",
					i, *font);
			}
#endif
			continue;
		}
		if (fsize > csize) {
			fsize = fsize - csize + 100;
					/* penalty for larger font */
		} else
			fsize = csize - fsize;
		if (error > fsize) {
			error = fsize;
			best = i;
			if (verbose) {
				fprintf(stderr, " [%d] <%s>: score %d best\n",
					i, *font, error);
			}
		} else {
			if (verbose) {
				fprintf(stderr, " [%d] <%s>: score %d\n",
					i, *font, error);
			}
		}
	}
	if (best >= 0) {
		if (verbose) {
			fprintf(stderr, "using best [%d] <%s>\n",
				best, fontlist[best]);
		}
		strncpy(fontstring, fontlist[best], sizeof(fontstring));
	} else if (scalable >= 0 || tscalable >= 0) {
		x_fontname(fontstring, sizeof(fontstring), xfont, csize,
			registry);
		if (verbose) {
			fprintf(stderr, "using %sscalable <%s>\n",
				tscalable >= 0 ? "true" : "", fontstring);
		}
	}
	xfontstruct = XLoadQueryFont(display, fontstring);

	if (freeindex < 0)
		XFreeFontNames(fontlist);

	/*
	 * Fill font cache.
	 */
	for (i = 0; i < FONTCACHEMAX; i++) {
		if (!fonts[i].xfontstruct)
			break;
	}
	if (FONTTYPEMAX <= i) {
		/* last resort.  always cache the font recently used */
		i = FONTTYPEMAX - 1;
		XFreeFont(display, fonts[i].xfontstruct);
		free(fonts[i].xfont);
		free(fonts[i].xlfd);
	}
#if 0
	if (verbose) {
		fprintf(stderr, "fill font cache: entry %d <%s>\n",
			i, fontstring);
	}
#endif
	fonts[i].csize = csize;
	fonts[i].registry = registry;
	fonts[i].xfont = strdup(xfont);
	fonts[i].xlfd = strdup(fontstring);
	fonts[i].xfontstruct = xfontstruct;

  gotfont:
	if (xfontstruct == NULL)
		return NULL;
	XSetFont(display, gcfore, xfontstruct->fid);
	return xfontstruct;
}

static u_int
draw_onechar_x(state, code, x, y, size, registry, lastchar)
	struct render_state *state;
	u_int code;
	int x, y;
	int size;
	char *registry;
	int lastchar;
{
	u_int charlen;
	static XFontStruct *xfontstruct;
	int coffset;
	XCharStruct *cs;
	char *metricsource;
	char *seed;

	seed = x_findseed(state, registry);
	xfontstruct = x_setfont(seed, char_size[caching], registry, NULL);
	if (xfontstruct == NULL)
		return 0;

	if (!xfontstruct->per_char) {
		metricsource = "max_bounds";
		coffset = 0;
		cs = &xfontstruct->max_bounds;
	} else if (!xfontstruct->min_byte1 && !xfontstruct->max_byte1) {
		metricsource = "bytewise offset";
		coffset = (code & 0xff) - xfontstruct->min_char_or_byte2;
		cs = &xfontstruct->per_char[coffset];
	} else {
		metricsource = "wordwise offset";
		coffset = (code & 0xff) - xfontstruct->min_char_or_byte2;
		coffset += (((code >> 8) & 0xff) - xfontstruct->min_byte1)
		    * (xfontstruct->max_char_or_byte2 - xfontstruct->min_char_or_byte2);
		cs = &xfontstruct->per_char[coffset];
	}

	/*
	 * It looks that there are some Japanese X11 fonts with bogus
	 * font metric (cs->width == 0).  This is a workaround for that.
	 * (or is there any mistake in above "coffset" computation?)
	 *
	 * TODO: report the X/Open group, or some other guys, about this.
	 */
#ifndef THAI
/*
 * 2000-07-19 Theppitak:
 * Comment out for Thai to properly render zero-width chars
 */
	if (!cs->width) {
		if (verbose) {
			fprintf(stderr, "X11 font %s:%d:%s has bogus "
				"font metric for glyph 0x%04x\n"
				"\tcs->width=%d, source=%s, coffset=0x%04x\n",
				seed, char_size, registry?registry:"NULL",
				code, cs->width, metricsource, coffset);
		}
		cs = &xfontstruct->max_bounds;
	}
#endif

	draw_line_itemsize(state, cs->ascent, cs->descent);

	/* usually */
	charlen = cs->width;

	/*
	 * for the very first char on the line, the char may goes over the
	 * edge at the lefthand side.  offset the image to the right so that
	 * whole part of the bitmap appears on the screen.
	 * beware the sign-ness of cs->lbearing.
	 */
	if (x + cs->lbearing < 0) {
		x -= cs->lbearing;
		charlen -= cs->lbearing;
	}

	/*
	 * For the last char, make sure that the whole part of the bitmap
	 * appears on the screen.
	 */
	if (lastchar && cs->width < cs->rbearing)
		charlen += cs->rbearing - cs->width;

	obj_new_xfont(state, x, y, size, code, registry);

	return charlen;
}

/*
 * render misc items.
 */
static void
back_gradation(state, cg0)
	struct render_state *state;
	struct ctrl_grad *cg0;
{
	struct ctrl_grad cg1;
	struct ctrl_grad *cg;
	int srcwidth, srcheight;
	int dstwidth, dstheight;
	int dir, numcolor;
	float xzoomrate, yzoomrate;
	int hquality, vquality;

	Image *myimage, *image;
	Pixmap mypixmap;
	XImageInfo *ximageinfo;
	byte *pic;
	int private = mgp_flag & FL_PRIVATE;
	static Cursor curs;

	/* okay, please wait for a while... */
	if (!curs)
		curs = XCreateFontCursor(display, XC_watch);
	XDefineCursor(display, window, curs);
	XFlush(display);

	/* just for safety */
	memcpy(&cg1, cg0, sizeof(struct ctrl_grad));
	cg = &cg1;

	/* grab parameters */
	dir = cg->ct_direction;
	numcolor = cg->ct_numcolor;
	hquality = b_quality[caching];
	vquality = b_quality[caching];

	/*
	 * XXX zoomflag is too complex to understand.
	 */
	if (!cg->ct_zoomflag) {
		int t;
		int i;

		dstwidth = state->width * cg->ct_width / 100;
		dstheight = state->height * cg->ct_height / 100;
		srcwidth = dstwidth;
		srcheight = dstheight;

		/*
		 * apply quality factor if srcwidth/height are large enough.
		 */
#define TOOSMALLFACTOR 8
		t = srcwidth;
		for (i = 100; hquality < i; i--) {
			t = srcwidth * i / 100;
			if (t < cg->ct_g_colors * TOOSMALLFACTOR)
				break;
		}
		srcwidth = t;

		t = srcheight;
		for (i = 100; vquality < i; i--) {
			t = srcheight * i / 100;
			if (t < cg->ct_g_colors * TOOSMALLFACTOR)
				break;
		}
		srcheight = t;
#undef TOOSMALLFACTOR
	} else {
		dstwidth = state->width;
		dstheight = state->height;
		srcwidth = state->width * cg->ct_width / 100;
		srcheight = state->height * cg->ct_height / 100;

		/*
		 * we don't apply quality factor here, since srcwidth/height
		 * is already smaller than dstwidth/height.
		 */
	}

#if 0
	if (srcwidth * hquality / 100 < cg->ct_g_colors * TOOSMALLFACTOR
	 || srcheight * vquality / 100 < cg->ct_g_colors * TOOSMALLFACTOR) {
		srcwidth = srcwidth * hquality / 100;
		srcheight = srcheight * vquality / 100;
	}
#endif

	xzoomrate = 100.0 * dstwidth / srcwidth;
	yzoomrate = 100.0 * dstheight / srcheight;

	/* performace enhance hack for special case */
	if (dir % 90 == 0) {
		float *q;
		int *p, *r;

		/*
		 * 0 or 180: reduce width
		 * 90 or 270: reduce height
		 */
		p = (dir % 180 == 0) ? &srcwidth : &srcheight;
		q = (dir % 180 == 0) ? &xzoomrate : &yzoomrate;
		r = (dir % 180 == 0) ? &dstwidth : &dstheight;

		/* rely upon use X11 background image tiling. */
		*q = (float) 100.0;
#ifndef DITHERED_BGRAD
		*p = 1;
		*r = 1;
#else
		*p = 3;
		*r = 3;
#endif
	}

	if (verbose) {
		fprintf(stderr, "raw: %d,%d qu: %d,%d "
			"dst: %d,%d src: %d,%d zoom: %0.2f,%0.2f\n",
			cg->ct_width, cg->ct_height,
			hquality, vquality,
			dstwidth, dstheight, srcwidth, srcheight,
			xzoomrate, yzoomrate);
	}

	screen = DefaultScreen(display);

	/* make gradation image */
	pic = draw_gradation(srcwidth, srcheight, cg);
	myimage = make_XImage(pic, srcwidth, srcheight);

	if (numcolor < 64)
		myimage = reduce(myimage, numcolor, verbose);

	if (verbose) {
		fprintf(stderr, "background zoomrate: (%0.2f,%0.2f)\n",
			xzoomrate, yzoomrate);
		fprintf(stderr, "background zoom mode %d: "
			"(%d, %d)->(%d, %d)[%d]\n", cg->ct_zoomflag,
			srcwidth, srcheight, dstwidth, dstheight, b_quality);
	}

	if (xzoomrate != 100.0 || yzoomrate != 100.0) {
		image = myimage;
		myimage = zoom(image, xzoomrate, yzoomrate, verbose);
		if (!image) {
			fprintf(stderr, "image zoom (%0.2fx%0.2f) failed in back_gradataion\n",
				xzoomrate, yzoomrate);
			exit(1);
		}
		freeImage(image);
	}

#ifndef COLOR_BUGFIX
	if (private) free_alloc_colors(&back_clr);
#endif
	ximageinfo = imageToXImage(display, screen, visual, depth, myimage,
		private, 0, 1, verbose);
	if (!ximageinfo) {
		fprintf(stderr, "Cannot convert Image to XImage\n");
		cleanup(-1);
	}

	regist_background_pixmap(ximageinfo, myimage);

	XUndefineCursor(display, window);
	XFlush(display);
}

static void
image_load(state, filename, numcolor, ximagesize, yimagesize, backflag, zoomflag, centerflag)
	struct render_state *state;
	char *filename;
	int numcolor;
	int ximagesize;
	int yimagesize;
	int backflag;
	int zoomflag;
	int centerflag;
{
	Image *image, *myimage;
	Pixmap mypixmap;
	XImageInfo *ximageinfo;
	u_int image_posx;
	int width, height;
	float xzoomrate, yzoomrate;
	int	private = mgp_flag & FL_PRIVATE;
	static Cursor curs;
	static char backfile[MAXPATHLEN];
	static int backzoom, backnumcolor, backx, backy;

	if (!caching){
		if (!curs)
			curs = XCreateFontCursor(display, XC_watch);
		XDefineCursor(display, state->target, curs);
		XFlush(display);
	}

	if ((myimage = loadImage(filename, verbose)) == NULL) {
		fprintf(stderr, "failed to load image file\n");
		cleanup(-1);
	}
	width = myimage->width;
	height = myimage->height;

	if (myimage->depth == 1 && myimage->trans < 0) {
		XColor xc;

		xc.flags = DoRed | DoGreen | DoBlue;
		xc.pixel = fore_color[caching];
		XQueryColor(display, colormap, &xc);
		*(myimage->rgb.red + 1) = xc.red;
		*(myimage->rgb.green + 1) = xc.green;
		*(myimage->rgb.blue + 1) = xc.blue;
		myimage->trans = 0;	/* call obj_image_trans() later */
	}

	if (numcolor)
		myimage = reduce(myimage, numcolor, verbose);

#if 0
	if (zoomflag == 2) {
		/*
		 * auto resize according to physical and desired screen size.
		 * allow 5% error for '-o' option.
		 */
		if (ximagesize == 0 || ximagesize == state->width)
			ximagesize = 100;
		else
			ximagesize = state->width * 100 / ximagesize;
		if (yimagesize == 0 || yimagesize == state->height)
			yimagesize = 100;
		else
			yimagesize = state->height * 100 / yimagesize;
		if (ximagesize > 95 && ximagesize < 105 &&
		    yimagesize > 95 && yimagesize < 105)
			ximagesize = yimagesize = 0;
	}
	if (ximagesize != 0) {
		if (!zoomflag)
			xzoomrate = state->width * ximagesize / width;
		else
			xzoomrate = ximagesize;
	} else
		xzoomrate = 100.0;
	if (yimagesize != 0) {
		if (!zoomflag)
			yzoomrate = state->height * yimagesize / height;
		else
			yzoomrate = yimagesize;
	} else
		yzoomrate = 100.0;
#else
	if (!ximagesize) ximagesize = 100;
	if (!yimagesize) yimagesize = 100;
	xzoomrate = (float) ximagesize;
	yzoomrate = (float) yimagesize;
	image_zoomratio(state, &xzoomrate, &yzoomrate, zoomflag, width, height);
#endif

	if (backflag) {
		if (xzoomrate != 100 || yzoomrate != 100) {
			image = myimage;
			myimage = zoom(image, xzoomrate, yzoomrate, verbose);
			if (!image) {
				fprintf(stderr, "image zoom (%dx%d) failed in image_load\n",
					xzoomrate, yzoomrate);
				exit(1);
			}
			freeImage(image);
		}

#ifndef COLOR_BUGFIX
		if (private) free_alloc_colors(&back_clr);
#endif
		ximageinfo= imageToXImage(display, screen, visual, depth,
				myimage, private, 0, 1, verbose);
		if (ximageinfo == NULL) {
			fprintf(stderr, "Cannot convert Image to XImage\n");
			cleanup(-1);
		}
		regist_background_pixmap(ximageinfo, myimage);
		goto end;
	}

	draw_line_itemsize(state, 0, height * yzoomrate / 100);
	if (centerflag)
		image_posx = char_size[caching] / 2 - (width * xzoomrate / 100) / 2;
	else
		image_posx = 0;

	obj_new_image(state, state->linewidth + image_posx,
		- height * yzoomrate / 100 / 2,
		myimage, xzoomrate, yzoomrate);
	state->linewidth += (width * xzoomrate / 100);
end:
	if (!caching){
		XUndefineCursor(display, state->target);
		XFlush(display);
	}
}

static void
image_load_ps(state, filename, numcolor, ximagesize, yimagesize, backflag, zoomflag, centerflag)
	struct render_state *state;
	char *filename;
	int numcolor;
	int ximagesize;
	int yimagesize;
	int backflag;
	int zoomflag;
	int centerflag;
{
	int x1, y1, x2, y2;
	static Cursor curs;
	char fullname[MAXPATHLEN];
	char *imagefile;
	int width, height;
	float xzoom, yzoom;
	char *p;

	/* wait for a while, please. */
	if (!curs)
		curs = XCreateFontCursor(display, XC_watch);
	XDefineCursor(display, window, curs);
	XFlush(display);

	if (findImage(filename, fullname) < 0) {
		fprintf(stderr, "image file %s not found in path\n", filename);
		cleanup(-1);
	}
	if (ps_boundingbox(fullname, &x1, &y1, &x2, &y2) < 0) {
		/* error message generated in ps_boundingbox() */
		cleanup(-1);
	}

	width = x2 - x1 + 1;
	height = y2 - y1 + 1;
#if 0
	if (zoomflag == 2) {
		/* screen relative */
		if (ximagesize == 0 || ximagesize == state->width)
			ximagesize = 100;
		else
			ximagesize = state->width * 100 / ximagesize;
		if (yimagesize == 0 || yimagesize == state->height)
			yimagesize = 100;
		else
			yimagesize = state->height * 100 / yimagesize;
		if (ximagesize > 95 && ximagesize < 105 &&
		    yimagesize > 95 && yimagesize < 105)
			ximagesize = yimagesize = 0;
	}
	if (ximagesize != 0) {
		if (!zoomflag)
			xzoom = ((float) state->width * ximagesize) / width;
		else
			xzoom = (float) ximagesize;
		width = width * xzoom / 100;
	} else
		xzoom = 100.0;
	if (yimagesize != 0) {
		if (!zoomflag)
			yzoom = ((float) state->height * yimagesize) / height;
		else
			yzoom = yimagesize;
		height = height * yzoom / 100;
	} else
		yzoom = 100.0;
#else
	xzoom = (float) ximagesize;
	yzoom = (float) yimagesize;
	image_zoomratio(state, &xzoom, &yzoom, zoomflag, width, height);
	width = width * xzoom / 100;
	height = height * yzoom / 100;
#endif

	imagefile = epstoimage(state, fullname, x1, y1, width, height, xzoom,
		yzoom);
	if (imagefile == NULL) {
		fprintf(stderr, "WARN: cannot generate %s file from %s\n",
			gsdevice, filename);
		XUndefineCursor(display, window);
		XFlush(display);
		return;
	}

	if (mgp_flag & FL_VERBOSE) {
		fprintf(stderr, "image_load_ps: %s: %s file = %s\n",
			filename, gsdevice, imagefile);
	}
	image_load(state, imagefile, numcolor, 100, 100, backflag,
		Z_NORMAL | (Z_NORMAL << Z_YSHIFT), centerflag);
	/* XXX: unlink imagefile in /tmp */
	if ((p = strrchr(imagefile, '/')) != NULL)
		p++;
	else
		p = imagefile;
	if (strncmp(p, ".gscache", sizeof(".gscache") - 1) != 0)
		unlink(imagefile);

	if (!backflag)
		image_setcolor(state);
}

void
timebar(state)
	struct render_state *state;
{
	int pos, n, p, barlen;
	GC *pgc;

	if (t_start == 0 || tbar_mode == 0 || caching)
		return;

	pos = (state->width - 2) * (state->page - 1) / (maxpage - 1);
	p = (time(NULL) - t_start) * 100;
	barlen = state->width - state->width * p / t_fin / 6000;

	if (state->width * 50 / 100 < barlen)
		pgc = &gcgreen;
	else if (state->width * 30 / 100 < barlen)
		pgc = &gcyellow;
	else
		pgc = &gcred;
	if (barlen > 0) {
		XClearArea(display, state->target, 0, state->height - 2,
			state->width, 2, 0);
		XFillRectangle(display, state->target, *pgc,
			state->width - barlen, state->height - 1, barlen, 1);
		XFillRectangle(display, state->target, *pgc,
			pos, state->height - 5, 2, 5);
	} else if (barlen < 0) {
		barlen = - barlen;
		n = p / t_fin / 6000;
		if (n > state->height - 1)
			n = state->height - 1;
		if (n)
			XFillRectangle(display, state->target, gcred,
				0, state->height - n,
				barlen, n);
		XClearArea(display, state->target, 0, state->height - (n + 2),
			state->width, n + 2, 0);
		XFillRectangle(display, state->target, gcred,
			0, state->height - (n + 1),
			barlen % state->width, n + 1);
		XFillRectangle(display, state->target, gcred,
			pos, state->height - (n + 1 + 4),
			2, 5);
	}
}

static void
process_icon(state, cp)
	struct render_state *state;
	struct ctrl *cp;
{
	u_int i, icon_type, icon_size, icon_x, icon_y, index;
	u_long tmp_color;
	static struct ctl_words icon_words[] = {
		{ 1, 'x', "box", 3 },
		{ 2, 'x', "arc", 3 },
		{ 3, 'x', "delta1", 6 },
		{ 4, 'x', "delta2", 6 },
		{ 5, 'x', "delta3", 6 },
		{ 6, 'x', "delta4", 6 },
		{ 7, 'x', "dia", 3 },
		{ 0, 'x', NULL, 0 }
	};
	XPoint xpoint[4];
	static struct icon_point {
		int	point_num;
		XPoint xpoint[4];
	} icon_point[] = {{ 3, {{1, 0}, {0, 2}, {2, 2}, {0, 0}}},
			  { 3, {{0, 0}, {2, 0}, {1, 2}, {0, 0}}},
			  { 3, {{0, 0}, {0, 2}, {2, 1}, {0, 0}}},
			  { 3, {{2, 0}, {2, 2}, {0, 1}, {0, 0}}},
			  { 4, {{1, 0}, {0, 1}, {1, 2}, {2, 1}}}};
		
	for (i = 0; icon_words[i].ctl_strlen != 0; i++) {
		if (!strncasecmp(cp->ctic_value, icon_words[i].ctl_string,
			strlen(cp->ctic_value))) {
				break;
		}
	}

	icon_type = icon_words[i].ctl_type; /* may be 0 */
	icon_size = char_size[caching] * cp->ctic_size / 100;

	switch(icon_type){
	case 0:
		/* this is image */
		icon_x = icon_size * 100 / state->width;
		icon_y = icon_size * 100 / state->height;
		if (icon_x == 0) icon_x = 1;
		if (icon_y == 0) icon_y = 1;
		tmp_color = fore_color[caching];
		fore_color[caching] = cp->ctic_color;
		image_load(state, cp->ctic_value, 0, icon_x, icon_y, 0, 0, 1);
		fore_color[caching] = tmp_color;
		break;

	case 1:
		/* this is box */
		obj_new_icon(state,
			state->linewidth + char_size[caching]/2 - icon_size/2,
			POSY(icon_size), icon_type, icon_size,
			cp->ctic_color, 0, NULL);
		state->linewidth += char_size[caching];
		break;

	case 2:
		/* this is arc */
		obj_new_icon(state,
			state->linewidth + char_size[caching]/2 - icon_size/2,
			POSY(icon_size), icon_type, icon_size, 
			cp->ctic_color, 0, NULL);
		state->linewidth += char_size[caching];
		break;

	case 3:
	case 4:
	case 5:
	case 6:
	case 7:
		index = icon_type - 3;
		icon_x = state->linewidth + (char_size[caching] - icon_size) / 2;
#if 0
		icon_y = POSY(icon_size);
#else
		icon_y = 0;
#endif
		for (i = 0; i < icon_point[index].point_num; i ++){
			xpoint[i].x = icon_x +
				icon_point[index].xpoint[i].x * icon_size / 2;
			xpoint[i].y = icon_y +
				icon_point[index].xpoint[i].y * icon_size / 2;
		}
		obj_new_icon(state, 0, 0, icon_type, icon_size, 
			cp->ctic_color, icon_point[index].point_num, xpoint);
		state->linewidth += char_size[caching];
		break;

	default:
		break;
	}

	cp = NULL;
}

static void
draw_bar(state, cp)
	struct render_state *state;
	struct ctrl *cp;
{
	u_int width, swidth, st, len;
	XColor col, scol;
	static GC gcbar, gcsbar;
	static u_long prevcolor = -1;

	if (!gcbar) {
		gcbar = XCreateGC(display, state->target, 0, 0);
		XSetFunction(display, gcbar, GXcopy);
		gcsbar = XCreateGC(display, state->target, 0, 0);
		XSetFunction(display, gcsbar, GXcopy);
	}
	col.pixel = cp->ctb_color;
	if (col.pixel == -1)
		col.pixel = fore_color[caching];
	if (col.pixel != prevcolor) {
		prevcolor = col.pixel;
		col.flags = DoRed|DoGreen|DoBlue;
		XQueryColor(display, colormap, &col);
		scol.red   = col.red   / 2;
		scol.green = col.green / 2;
		scol.blue  = col.blue  / 2;
		if (!XAllocColor(display, colormap, &scol))
			scol.pixel = col.pixel;
		XSetForeground(display, gcbar, col.pixel);
		XSetForeground(display, gcsbar, scol.pixel);
	}
	width = cp->ctb_width * state->height / 1000;
	swidth = width / 2;
	width -= swidth;
	st = cp->ctb_start * state->width / 100;
	len = cp->ctb_length * state->width / 100;
	XFillRectangle(display, state->target, gcbar, st, state->ypos, len, width);
	XFillRectangle(display, state->target, gcsbar, st, state->ypos + width, len, swidth);

	state->ypos += width + swidth + VERT_GAP(char_size[caching]) / 2;
	state->maxascent = width + swidth;
}

static void
process_system(state, cp)
	struct render_state *state;
	struct ctrl *cp;
{
	pid_t pid;
	int i;
	char **argv;
	char buf[BUFSIZ];

	if (mgp_flag & FL_NOFORK) {
		if (mgp_flag & FL_VERBOSE) {
			fprintf(stderr, "WARN: %%system ");
			for (i = 0; i < cp->cta_argc; i++) {
				fprintf(stderr, "%c%s", (i == 0) ? '"' : ' ',
					cp->cta_argv[i]);
			}
			fprintf(stderr, "\": directive skipped\n");
		}
		return;
	}

	if (checkchild(cp) != (pid_t)-1)
		return;	/*already running*/

	/*
	 * edit argument.
	 * if we have X11 geometry string 
	 */
	argv = (char **)cp->cta_argv;
	for (i = 0; i < cp->cta_argc; i++) {
		if (*(argv[i]) == '%')
			break;
	}
	if (i < cp->cta_argc) {
		char *p;
		char *q;
		int myxpos, myypos;
		int rootxsiz, rootysiz;
		int xsiz, ysiz;
		int xloc, yloc;
		int mode;

	    {
		XWindowAttributes wa;
		Window junkwin;
		int junk;

		XGetWindowAttributes(display, window, &wa);
		XTranslateCoordinates(display, window, wa.root,
			-wa.border_width, -wa.border_width,
			&myxpos, &myypos, &junkwin);
		XGetGeometry(display, wa.root, &junkwin, &junk, &junk,
			&rootxsiz, &rootysiz, &junk, &junk);
	     }

		argv = (char **)malloc((cp->cta_argc + 1) * sizeof(char *));
		memcpy(argv, cp->cta_argv, (cp->cta_argc + 1) * sizeof(char *));
		p = argv[i];
		p++;	/*drop percent char*/
		q = buf;
		*q = '\0';

		mode = XParseGeometry(p, &xloc, &yloc, &xsiz, &ysiz);
		if (mode == 0)
			goto fail;
		if ((mode & WidthValue) && (mode & HeightValue)) {
			sprintf(q, "%dx%d", xsiz * state->width / 100,
				ysiz * state->height / 100);
			q += strlen(q);
		}
		if ((mode & XValue) && (mode & YValue)) {
			xloc = xloc * state->width / 100;
			yloc = yloc * state->height / 100;
			if (mode & XNegative)
				xloc = rootxsiz - myxpos + state->width - xloc;
			else
				xloc += myxpos;
			if (mode & YNegative)
				yloc = rootysiz - myypos + state->height - yloc;
			else
				yloc += myypos;
			sprintf(q, "+%d+%d", xloc, yloc);
		}

		if (mgp_flag & FL_VERBOSE) {
			fprintf(stderr, "relative geometry: "
				"%s (presentation %dx%d+%d+%d)\n",
				argv[i], state->width, state->height,
				myxpos, myypos);
			fprintf(stderr, "\t-> %s\n", buf);
		}
		argv[i] = buf;

		if (0) {
fail:
			if (mgp_flag & FL_VERBOSE) {
				fprintf(stderr,
					"relative geometry: %s failed\n",
					argv[i]);
			}
		}
	}
	pid = fork();
	if (pid < 0) {
		perror("fork");
		cleanup(-1);
	} else if (pid == 0) {
		execvp(argv[0], argv);
		perror(argv[0]);
		_exit(1);
	}

	if (!cp->cta_flag)	/*will be purged at the end of page*/
		regchild(pid, cp, -1, state->page);
	else
		regchild(pid, cp, -1, cp->cta_flag);
}

static void
process_xsystem(state, cp)
	struct render_state *state;
	struct ctrl *cp;
{
	pid_t pid;
	int i, dumint;
	int xloc, yloc;
	int xsiz, ysiz;
	char **argv;
	char buf[BUFSIZ];
	Window window_id, dumwin;

	if (mgp_flag & FL_NOFORK) {
		if (mgp_flag & FL_VERBOSE) {
			fprintf(stderr, "WARN: %%system ");
			for (i = 0; i < cp->cta_argc; i++) {
				fprintf(stderr, "%c%s", (i == 0) ? '"' : ' ',
					cp->cta_argv[i]);
			}
			fprintf(stderr, "\": directive skipped\n");
		}
		return;
	}


	/*
	 * edit argument.
	 * if we have X11 geometry string 
	 */
	argv = (char **)cp->cta_argv;
	for (i = 0; i < cp->cta_argc; i++) {
		if (!strncmp(argv[i], "-geom", 5))
			break;
	}
	i ++;
	if (i < cp->cta_argc) {
		char *p;
		char *q;
		int mode;

		argv = (char **)malloc((cp->cta_argc + 1) * sizeof(char *));
		memcpy(argv, cp->cta_argv, (cp->cta_argc + 1) * sizeof(char *));
		p = argv[i];
		if (*p == '%') p++;	/*drop percent char*/
		q = buf;
		*q = '\0';

		mode = XParseGeometry(p, &xloc, &yloc, &xsiz, &ysiz);
		if (mode == 0)
			goto fail;
		if ((mode & WidthValue) && (mode & HeightValue)) {
			xsiz = xsiz * state->width / 100;
			ysiz = ysiz * state->height / 100;
			sprintf(q, "%dx%d", xsiz, ysiz);
			q += strlen(q);
		}
		/* make window raise outside of display */
		sprintf(q, "+%d+%d", DisplayWidth(display, DefaultScreen(display)),
						DisplayHeight(display, DefaultScreen(display)));

		if (mgp_flag & FL_VERBOSE) {
			fprintf(stderr, "relative geometry: "
				"%s (presentation %dx%d+%d+%d)\n",
				argv[i], state->width, state->height,
				xloc, yloc);
			fprintf(stderr, "\t-> %s\n", buf);
		}
		argv[i] = buf;

		if (0) {
fail:
			if (mgp_flag & FL_VERBOSE) {
				fprintf(stderr,
					"relative geometry: %s failed\n",
					argv[i]);
			}
		}
	} else {
		char geom_arg1[] = {"-geometry"};
		char geom_arg2[512];

		sprintf(geom_arg2, "+%d+%d", DisplayWidth(display, 
			DefaultScreen(display)),
			DisplayHeight(display, DefaultScreen(display)));

		argv[cp->cta_argc] = geom_arg1;
		argv[cp->cta_argc+1] = geom_arg2;
	}

	if ((window_id = checkchildwin(cp)) != (Window)-1)
		goto finish;	/*already running*/

	if (checkchild(cp) != (pid_t)-1)
		return;	/*already running*/

	pid = fork();
	if (pid < 0) {
		perror("fork");
		cleanup(-1);
	} else if (pid == 0){
		usleep(EXEC_DELAY);
		execvp(argv[0], argv);
		perror(argv[0]);
		_exit(1);
	}

	window_id = search_child_window();

	if (!cp->cta_flag)	/*will be purged at the end of page*/
		regchild(pid, cp, window_id, state->page);
	else
		regchild(pid, cp, window_id, cp->cta_flag);

	if (window_id != -1)
		reparent_child_window(window_id, window_width, window_height);
	else {
		if (mgp_flag & FL_VERBOSE) {
			fprintf(stderr, "WARN: %%xsystem can not find child window:");
			for (i = 0; i < cp->cta_argc; i++) {
				fprintf(stderr, "%c%s", (i == 0) ? '"' : ' ',
					cp->cta_argv[i]);
			}
			fprintf(stderr, "\"\n");
		}
		return;
	}

finish:
	XGetGeometry(display, window_id, &dumwin, 
		&xloc, &yloc, &xsiz, &ysiz, &dumint, &dumint);
	state->linewidth = xsiz;
	xloc = set_position(state);
	yloc = state->ypos;
	XMoveWindow(display, window_id, xloc, yloc);
	state->ypos += ysiz;

#if 0 /* not implemented yet */
	state->linewidth += xsiz;
	state->maxascent += ysiz;
#endif
}


Window 
search_child_window()
{
	XEvent e;
	int	fd, found = 0;
	fd_set fdset, dumfdset; 
	struct timeval timeout;

	fd = ConnectionNumber(display);
	/* waiting for 2 second */
	timeout.tv_sec = 2;
	timeout.tv_usec = 0;

	/* get all client's ReparentNotify event */
	XSelectInput(display, DefaultRootWindow(display),
		SubstructureNotifyMask);

	while (!found) {
		while (XEventsQueued(display, QueuedAfterFlush) > 0) {
			XNextEvent(display, &e);
			if (e.type == ReparentNotify){
				found = 1;
				break;
			}
		}
		if (found) break;
		FD_ZERO(&fdset);
		FD_SET(fd, &fdset); 	
		FD_ZERO(&dumfdset);
		if (!select(fd+1, &fdset, &dumfdset, &dumfdset, &timeout))
			break;
	}

	XSelectInput(display, DefaultRootWindow(display), NoEventMask);

	if (found == 1)
		return e.xreparent.window;
	else
		return (Window)-1;
}

void 
reparent_child_window(child_window, x, y)
	Window	child_window;
	int	x,y;
{
	Window	dummyroot, *dummywin;
	Window	target, parent;
	u_int	dumint;

	target = child_window;
	while (1) {
		XQueryTree(display, target, &dummyroot, &parent, &dummywin,
			&dumint);
		if (parent == dummyroot)	
			break;
		XFree(dummywin);
		target = parent;
	}
	XReparentWindow(display, child_window, window, x, y);
	XDestroyWindow(display, target);
}

void
draw_reinit(state)
	struct render_state *state;
{
	/* invalidate the background image cache */

	bg_ctl_last = NULL;
	x_registerseed(state, NULL, NULL);
}

static char *
epstoimage(state, epsfile, x, y, width, height, xzoom, yzoom)
	struct render_state *state;
	char *epsfile;
	int x, y, width, height, xzoom, yzoom;
{
	int fd, pfd[3][2];
	int i, j;
	FILE *fp;
	int status;
	pid_t pid = 0, gspid;
	char *cp;
	int scale = 1;
	struct stat stbuf;
	char geom[32], device[64], scalebuf[32];
	static char imagefile[MAXPATHLEN];
	void (*sigpipe_handler)();

	fd = -1;
	for (i = 0; i < 3; i++) {
		for (j = 0; j < 2; j++)
			pfd[i][j] = -1;
	}
	strcpy(imagefile, epsfile);
	if ((cp = strrchr(imagefile, '/')) != NULL)
		cp++;
	else
		cp = imagefile;
	sprintf(cp, ".gscache.%s.%dx%d", epsfile + (cp - imagefile),
		width, height);
	if (verbose)
		fprintf(stderr, "gs cache filename: %s\n", imagefile);

	/* check if we got any cached image file already. */
	if (stat(imagefile, &stbuf) == 0) {
		time_t cachetime;
		off_t cachesize;

		cachetime = stbuf.st_mtime;
		cachesize = stbuf.st_size;
		if (stat(epsfile, &stbuf) == 0) {
			if (stbuf.st_mtime < cachetime && cachesize > 0) {
				if (verbose) {
					fprintf(stderr, "gs cache valid, "
						"using it \n");
				}
				return imagefile;
			}
		}
		if (verbose) {
			fprintf(stderr, "gs cache looks older than source, "
				"generate again\n");
		}
	} else {
		if (verbose) {
			fprintf(stderr, "gs cache not found, convert eps\n");
		}
	}

	if (verbose)
		fprintf(stderr, "converting eps file...\n");

	/* convert eps file into readable form. */
	sprintf(device, "-sDEVICE=%s", gsdevice);

	/*
	 * a suffix of +scale in the device tipe means produce a larger
	 * image that can be scaled later for better antialiasing.
	 */
	if ((cp = strchr(device, '+')) != NULL) {
		*cp++ = '\0';
		scale = atoi(cp);
		if (scale <= 0)
			scale = 2;
		xzoom *= scale;
		yzoom *= scale;
		width *= scale;
		height *= scale;
	}
	if (width == 0 || height == 0) {
		fprintf(stderr, "WARN: epstoimage: scale=%d, xzoom=%d, "
			"yzoom=%d, width=%d, height=%d\n",
			scale, xzoom, yzoom, width, height);
		return NULL;
	}
	if (scale != 1)
		sprintf(scalebuf, "%f", 1. / (double)scale);
	sprintf(geom, "-g%dx%d", width, height);

	/* generate cache file. */
	fd = open(imagefile, O_RDWR|O_CREAT|O_TRUNC, 0600);
	if (fd < 0) {
		/* last resort: generate output onto /tmp. */
		if ((cp = getenv("TMPDIR")) == NULL)
			cp = "/tmp";
		if (verbose) {
			fprintf(stderr, "could not write to \"%s\", using "
				"%s\n", imagefile, cp);
		}
		strcpy(imagefile, cp);
		strcat(imagefile, "/mgp.XXXXXXXX");
		if ((fd = mkstemp(imagefile)) < 0) {
			perror(imagefile);
			return NULL;
		}
	}
	if (scale != 1) {
		if (pipe(pfd[2]) < 0) {
			perror("pipe");
			goto error;
		}
		if ((pid = vfork()) == 0) {
			close(pfd[2][1]);
			dup2(pfd[2][0], 0); close(pfd[2][0]);
			dup2(fd, 1); close(fd);

			if (verbose)
				fprintf(stderr, "epstoimage: \"pnmdepth 256\"\n");
			close(2); /* XXX suppress message */
			execlp("pnmdepth", "pnmdepth", "255", NULL);
			perror("pnmdepth");
			_exit(1);
		}
		if (pid < 0) {
			perror("vfork");
			goto error;
		}
		close(pfd[2][0]); pfd[2][0] = -1;
		close(fd); fd = -1;

		if (pipe(pfd[1]) < 0) {
			perror("pipe");
			goto error;
		}
		if ((gspid = vfork()) == 0) {
			close(pfd[1][1]);
			dup2(pfd[1][0], 0); close(pfd[1][0]);
			dup2(pfd[2][1], 1); close(pfd[2][1]);

			if (verbose)
				fprintf(stderr, "epstoimage: \"pnmscale %s\"\n", scalebuf);
			close(2); /* XXX suppress message */
			execlp("pnmscale", "pnmscale", scalebuf, NULL);
			perror("pnmscale");
			_exit(1);
		}
		if (gspid < 0) {
			perror("vfork");
			goto error;
		}
		close(pfd[2][1]); pfd[2][1] = -1;
		close(pfd[1][0]); pfd[1][0] = -1;
		fd = pfd[1][1]; pfd[1][1] = -1;
	}
	if (pipe(pfd[0]) < 0) {
		perror("pipe");
		goto error;
	}
	if ((gspid = vfork()) == 0) {
		close(pfd[0][1]);
		dup2(pfd[0][0], 0); close(pfd[0][0]);
		dup2(fd, 1); close(fd);

		if (verbose)
			fprintf(stderr, "epstoimage: \"gs %s %s -sOutputFile=- -q -\"\n", geom, device);
		execlp("gs", "gs", geom, device, "-sOutputFile=-", "-q", "-", NULL);
		perror("gs");
		_exit(1);
	}
	close(fd); fd = -1;
	close(pfd[0][0]); pfd[0][0] = -1;

	if ((fp = fdopen(pfd[0][1], "w")) == NULL) {
		fprintf(stderr, "fdopen failed\n");
		goto error;
	}
	sigpipe_handler = signal(SIGPIPE, SIG_IGN);	/* XXX: avoid SIGPIPE */
	pfd[0][1] = -1;
	fprintf(fp, "%f %f scale\n", (double)xzoom/100., (double)yzoom/100.);
	fprintf(fp, "%d %d translate\n", -1 * x, -1 * y);
	fprintf(fp, "(%s) run\n", epsfile);
	fprintf(fp, "showpage\n");
	fprintf(fp, "quit\n");
	fsync(fd);
	fclose(fp);
	signal(SIGPIPE, sigpipe_handler);

	if (!pid)
		pid = gspid;
	while (waitpid(pid, &status, 0) < 0) {
		if (errno != EINTR)
			break;
	}
	if (stat(imagefile, &stbuf) == 0 && stbuf.st_size > 0)
		return imagefile;

  error:
	if (fd >= 0) close(fd);
	for (i = 0; i < 3; i++) {
		for (j = 0; j < 2; j++)
			if (pfd[i][j] >= 0)
				close(pfd[i][j]);
	}
	if (imagefile[0])
		unlink(imagefile);
	return NULL;
}

static void
image_setcolor(state)
	struct render_state *state;
{
	struct render_object *obj;
	Image *image;
	int i;
	Intensity *red, *green, *blue;
	XColor fore, back;

	obj = state->objlast;
	if (obj->type != O_IMAGE)
		return;

	image = obj->data.image.image;
	if (image->trans >= 0)
		return;

	switch (image->type) {
	case IBITMAP:
		/*
		 * XXX: Actually, no one comes here.
		 *      This translation for IBITMAP was done by image_load().
		 */
		fore.pixel = fore_color[caching];
		fore.flags = DoRed | DoGreen | DoBlue;
		XQueryColor(display, colormap, &fore);
		image->rgb.red  [1] = fore.red;
		image->rgb.green[1] = fore.green;
		image->rgb.blue [1] = fore.blue;
		image->trans = 0;
		break;

	case IRGB:
		red   = image->rgb.red;
		green = image->rgb.green;
		blue  = image->rgb.blue;
		for (i = 0; i < image->rgb.used; i++) {
			if (red[i] != green[i] || red[i] != blue[i])
				return;
		}
		/* grayscale */

		fore.pixel = fore_color[caching];
		fore.flags = DoRed | DoGreen | DoBlue;
		XQueryColor(display, colormap, &fore);

		if (!COMPLEX_BGIMAGE) {
			back.pixel = back_color[caching];
			back.flags = DoRed | DoGreen | DoBlue;
			XQueryColor(display, colormap, &back);
		} else {
			int  x, y, bpl;
			byte *p;
			Pixel d;

			/* XXX: use background color of center position */
			x = (obj->x + image->width/2) % bgpixmap[bgindex].image->width;
			y = (state->ypos + image->height/2) 
					% bgpixmap[bgindex].image->height;
			bpl = bgpixmap[bgindex].image->pixlen;
			p = bgpixmap[bgindex].image->data 
				+ (bgpixmap[bgindex].image->width * y + x) * bpl;
			d = memToVal(p, bpl);
			if (bgpixmap[bgindex].image->type == ITRUE) {
				back.red   = TRUE_RED(d) << 8;
				back.green = TRUE_GREEN(d) << 8;
				back.blue  = TRUE_BLUE(d) << 8;
			} else {
				back.red   = bgpixmap[bgindex].image->rgb.red  [d];
				back.green = bgpixmap[bgindex].image->rgb.green[d];
				back.blue  = bgpixmap[bgindex].image->rgb.blue [d];
			}
		}
		for (i = 0; i < image->rgb.used; i++) {
			if (red[i] >= 65000)	/*XXX*/
				image->trans = i;
			red[i]   = (back.red   * red  [i]
				  + fore.red   * (65535-red  [i])) / 65535;
			green[i] = (back.green * green[i]
				  + fore.green * (65535-green[i])) / 65535;
			blue[i]  = (back.blue  * blue [i]
				  + fore.blue  * (65535-blue [i])) / 65535;
		}
		break;

	case ITRUE:
#ifdef notdef
		/* How to inverse black & white? */
		image->trans =
		    RGB_TO_TRUE((Intensity)-1, (Intensity)-1, (Intensity)-1);
#endif
		break;
	}
}

#ifdef FREETYPE
static u_int
draw_onechar_tf(state, code, x, y, size, registry, lastchar, charset16)
	struct render_state *state;
	u_int code;
	int x, y;
	u_int size;
	char *registry;
	int lastchar;
	int	charset16;
{
	struct tfont *tfc;
	int charlen;

	tfc = tfc_get(code, size, 1, registry, charset16);
	draw_line_itemsize(state, tfc->ascent, tfc->descent);

	/* usually */
	charlen = tfc->charlen;

	/*
	 * for the very first char on the line, the char may goes over the
	 * edge at the lefthand side.  offset the image to the right so that
	 * whole part of the bitmap appears on the screen.
	 * beware the sign-ness of tfc->xoff.
	 */
	if (x + tfc->xoff < 0) {
		x -= tfc->xoff;
		charlen -= tfc->xoff;
	}

	/*
	 * For the last char, make sure that the whole part of the bitmap
	 * appears on the screen.
	 */
	if (lastchar && tfc->charlen < tfc->xoff + tfc->width)
		charlen += tfc->xoff + tfc->width - tfc->charlen;

	/*
	 * (x, y): left side, baseline of the font (FreeType font origin)
	 */
	obj_new_tfont(state, x, y, tfc);

	return charlen;
}
#endif /* FREETYPE */

static void
x_registerseed(state, seed, registry)
	struct render_state *state;
	char *seed;
	char *registry;
{
	char tmp1[BUFSIZ], tmp2[BUFSIZ];
	char *p;
	struct ctrl *cp;
	int hyphen;

	/* if both of arguments are NULL, initialize */
	if (!seed && !registry) {
		if (state->xfont)
			ctlfree(state->xfont);
		state->xfont = NULL;
		return;
	}

	if (!registry)
		registry = "iso8859-1";

	/* canonicalize seed */
	hyphen = 0;
	for (p = seed; *p; p++) {
		if (*p == '-')
			hyphen++;
	}
	switch (hyphen) {
	case 0:
		/* maybe alias, don't canonicalize */
		break;
	case 1:
		sprintf(tmp1, "%s-*", seed);
		seed = tmp1;
		break;
	case 2:
	case XLFD_HYPHEN:
		/* as is */
		break;
	default:
		fprintf(stderr, "invalid XFONT seed <%s>\n", seed);
		break;
	}

	/* canonicalize registry */
	if (!registry)
		registry = "iso8859-1";
	hyphen = 0;
	for (p = registry; *p; p++) {
		if (*p == '-')
			hyphen++;
	}
	switch (hyphen) {
	case 0:
		sprintf(tmp2, "%s-*", registry);
		registry = tmp2;
		break;
	case 1:
		/* as is */
		break;
	default:
		fprintf(stderr, "invalid XFONT registry <%s>\n", registry);
		exit(1);
	}

	cp = NULL;
	for (cp = state->xfont; cp; cp = cp->ct_next) {
		if (!cp->ctc2_value2) continue;
		if (strcmp(cp->ctc2_value2, registry) == 0)
			break;
	}
	if (cp) {
		free(cp->ctc2_value1);
		cp->ctc2_value1 = strdup(seed);
	} else {
		cp = ctlalloc1(CTL_XFONT2);
		cp->ctc2_value1 = strdup(seed);
		cp->ctc2_value2 = strdup(registry);
		cp->ct_next = state->xfont;
		state->xfont = cp;
	}
}

static char *
x_findseed(state, registry)
	struct render_state *state;
	char *registry;
{
	struct ctrl *cp;

	if (!registry)
		registry = "iso8859-1";
	for (cp = state->xfont; cp; cp = cp->ct_next) {
		if (strcmp(cp->ctc2_value2, registry) == 0) {
			return cp->ctc2_value1;
		}
	}
	return "*-*-*";		/*anything, canonicalized*/
}

/* cache specified page */
static void
cache_page(state, page)
	struct render_state *state;
	int page;
{
	/* we don't need caching */
	if (cached_page == page || page > maxpage || page <= 0)
		return;

	XFlush(display);
	memset(state, 0, sizeof(struct render_state));
	state->target = cachewin;  /*XXX*/
	state->width = window_width;
	state->height = window_height;
	state->page = page;
	caching = 1;
	if (verbose){
		printf("now caching %d page ...\n", page);
		fflush(stdout);
	}
	draw_page(state, NULL);
	if (verbose){
		printf("caching done \n");
	}
	caching = 0;
	cached_page = page;
}

static void
set_from_cache(state)
	struct render_state *state;
{
	int	i;

	char_size[0] = char_size[1];
	horiz_gap[0] = horiz_gap[1];
	vert_gap[0] = vert_gap[1];
	fore_color[0] = fore_color[1];
	back_color[0] = back_color[1];
	ctrl_color[0] = ctrl_color[1];
	b_quality[0] = b_quality[1];

	memcpy(state, &cache_state, sizeof(struct render_state));	
	state->target = window;

	XSetForeground(display, gcfore, fore_color[0]);
	XSetBackground(display, gcfore, back_color[0]);
	bg_ctl = bg_ctl_last = bg_ctl_cache;
	set_background_pixmap(bg_ctl);

	switch(cache_effect){
		case 1:
			cache_effect1();
			break;
		case 2:
			cache_effect2();
			break;
		default:
			break;
	}
	XCopyArea(display, cachewin, window, gc_cache, 
		0, 0, window_width, window_height, 0, 0);
	XFlush(display);
}

void
reset_background_pixmap()
{
	int	i = 0;

	bg_ctl_last = NULL;

	for (i = 0; i < MAXBGPIXMAP; i ++) {
		if (bgpixmap[i].image){
			XFreePixmap(display, bgpixmap[i].pixmap); 
			freeXImage(bgpixmap[i].image, bgpixmap[i].ximageinfo);
			freeImage(bgpixmap[i].image);
		}
		bgpixmap[i].ctl = NULL;
		bgpixmap[i].image = NULL;
		bgpixmap[i].ximageinfo = NULL;
	}
}

static void
cache_effect1()
{
	int x, step;

	step = cache_value ? window_width / cache_value : 1;
	if (!step) step = 1;

	for (x = window_width; x > step; x -= step){
		XCopyArea(display, window, window, gc_cache,
			step, 0,  window_width - step, window_height, 0, 0);

		XCopyArea(display, cachewin, window, gc_cache,
			window_width - x, 0, step, window_height, 
			window_width - step, 0);

		XFlush(display);
	}
}

static void
cache_effect2()
{
	int x, step;

	step = cache_value ? window_width / (cache_value * 2) : 1;
	if (!step) step = 1;

	for (x = 0; x < window_width; x += step){
		XCopyArea(display, window, window, gc_cache,
			x, 0,  window_width - step -x , window_height, x + step, 0);

		XCopyArea(display, cachewin, window, gc_cache,
			x, 0, step, window_height, x, 0);

		XFlush(display);
	}
}

/*
	pcache directive process
*/
static void
pcache_process(page)
int	page;
{
	if (!pcache.flag)
		return;

	if (pcache.page != page)
		return;

	if (pcache.mgpflag)
		mgp_flag |= FL_FRDCACHE;
	else 
		mgp_flag ^= FL_FRDCACHE;
	cache_mode   = pcache.mode;
	cache_effect = pcache.effect;
	cache_value  =  pcache.value;
	pcache.flag  = 0;
}


/*
	predraw: if this page contains texts only, 
			   draw page in pixmap once, then copy to window.
*/
static void
predraw(state)
	struct render_state *state;
{
	if (!caching && cached_page != state->page 
			&& page_attribute[state->page].pg_text){
		cache_page(&cache_state, state->page);
		set_from_cache(state);
		pcache_process(state->page);
	}
}


static void
get_background_pixmap(ctl, state)
	struct ctrl *ctl;
	struct render_state *state;
{
	int	i;

	/*
	 * check if background is already cached
	 */
	for (i = 0; i < MAXBGPIXMAP; i ++){
		if (bgpixmap[i].ctl && ctlcmp(ctl, bgpixmap[i].ctl) == 0){
			bgindex = i;
			return;			
		}
	}

	if (i == MAXBGPIXMAP){
		/* this background is not cached, we have to generate one */
		switch(ctl->ct_op){
		case CTL_BIMAGE: 
			image_load(state, ctl->ctm_fname, ctl->ctm_numcolor,
						ctl->ctm_ximagesize, ctl->ctm_yimagesize, 1,
						ctl->ctm_zoomflag, 0);
			break;
		case CTL_BGRAD:
			back_gradation(state, &ctl->ct_val.ctrl_grad);
			break;
		case CTL_BACK:
		default:
			break;
		}
	}
}

static void
regist_background_pixmap(ximageinfo, image)
	XImageInfo	*ximageinfo;
	Image		*image;
{
	Pixmap	pixmap;
	int	i, j;

	/* search empty slot */	
	for (i = 0; i < MAXBGPIXMAP; i ++){
		if (bgpixmap[i].ctl == NULL)
			break;
	}

	if (i == MAXBGPIXMAP){
		/* no empty slot, we need to make one  */
		XFreePixmap(display, bgpixmap[MAXBGPIXMAP -1].pixmap); 
		freeXImage(bgpixmap[MAXBGPIXMAP -1].image, 
					bgpixmap[MAXBGPIXMAP -1].ximageinfo);
		freeImage(bgpixmap[MAXBGPIXMAP -1].image);
		for (j = MAXBGPIXMAP -2; j >= 0; j --){
			bgpixmap[j +1].ctl = bgpixmap[j].ctl;
			bgpixmap[j +1].pixmap = bgpixmap[j].pixmap;
			bgpixmap[j +1].image = bgpixmap[j].image;
			bgpixmap[j +1].ximageinfo = bgpixmap[j].ximageinfo;
		}
		i = 0;
	}

	pixmap = ximageToPixmap(display, 
			RootWindow(display, screen), ximageinfo);
	bgpixmap[i].ctl = bg_ctl;
	bgpixmap[i].pixmap = pixmap;
	bgpixmap[i].image = image;
	bgpixmap[i].ximageinfo = ximageinfo;
	bgindex = i;
}

static void
set_background_pixmap(ctl)
	struct ctrl *ctl;
{
	int	i;

	switch(ctl->ct_op){
	case CTL_BIMAGE: 
	case CTL_BGRAD:
		for (i = 0; i < MAXBGPIXMAP; i ++){
			if (bgpixmap[i].ctl && ctlcmp(ctl, bgpixmap[i].ctl) == 0)
				break;	
		}
		if (i == MAXBGPIXMAP){
			fprintf(stderr, "fatal error in set_background_image()\n");
			cleanup(-1);
		}
		XSetWindowBackgroundPixmap(display, window, bgpixmap[i].pixmap);
		break;
	case CTL_BACK:
		XSetWindowBackground(display, window, ctl->ctl_value);
		break;
	default:
		fprintf(stderr, "fatal error in set_background_image()\n");
		cleanup(-1);
		break;
	}
}

/*
 * Clear target pixmap 
 */
static void
XClearPixmap(display, target)
	Display *display;
	Drawable target;
{
	int	i; 
	int x, y, width, height;
	XImage *xim;

	switch(bg_ctl->ct_op){
	case CTL_BIMAGE: 
	case CTL_BGRAD:
		for (i = 0; i < MAXBGPIXMAP; i ++){
			if (bgpixmap[i].ctl && ctlcmp(bg_ctl, bgpixmap[i].ctl) == 0)
				break;	
		}
		if (i == MAXBGPIXMAP){
			fprintf(stderr, "fatal error in set_background_image()\n");
			cleanup(-1);
		}

		xim = bgpixmap[i].ximageinfo->ximage;
		for (y = 0; y < window_height; y += xim->height)
			for (x = 0; x < window_width; x += xim->width)
				XPutImage(display, target, gc_cache, 
					xim, 0, 0, x, y, 
					xim->width, xim->height);
		break;
	case CTL_BACK:
		XSetForeground(display, gc_cache, bg_ctl->ctl_value);
		XFillRectangle(display, target,
			gc_cache, 0, 0, window_width, window_height);
		break;
	default:
		fprintf(stderr, "fatal error in set_background_image()\n");
		cleanup(-1);
		break;
	}
}

