/*
 * 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: font.c,v 1.26 2000/12/04 09:21:27 nishida Exp $
 */

#include "mgp.h"

#ifdef VFLIB

#define VF_DESCENT	8

int vfcachesize = 3000;	/*XXX*/
#define	VFHASH_SIZE	255

static char *vf_curname;
static int vfont_fd = -1;
static int vfcinitdone;
static int vfcachecnt;
static struct vfont vfclru;
static struct vfont vfcache[VFHASH_SIZE];
#define	VFCACHE_HASH(w, h, c)	(((c ^ w) ^ h) % VFHASH_SIZE)

int vfcachehit = 0;
int vfcachemiss = 0;

static void initrevbitmap __P((void));

/*
 * revbitmap: reverse the MSB and LSB of a byte value.
 *	example: revbitmap[0x80] == 0x01, revbitmap[0x55] == 0xaa
 * leftbitmap: the index of leftmost bit in a byte value. (MSB is on the left)
 *	example: leftbitmap[0x80] == 0, leftbitmap[0x19] == 3
 * rightbitmap: the index of rightmost bit in a byte value.
 *	example: rightbitmap[0x80] == 0, rightbitmap[0x19] == 7
 */
static u_char *revbitmap = NULL;
static char *leftbitmap = NULL;
static char *rightbitmap = NULL;

static short ctable[128] = {
	0x2121, 0x2121, 0x2121, 0x2121, 0x2121, 0x2121, 0x2121, 0x2121,
	0x2121, 0x2121, 0x2121, 0x2121, 0x2121, 0x2121, 0x2121, 0x2121,
	0x2121, 0x2121, 0x2121, 0x2121, 0x2121, 0x2121, 0x2121, 0x2121,
	0x2121, 0x2121, 0x2121, 0x2121, 0x2121, 0x2121, 0x2121, 0x2121,
	0x2121, 0x212a, 0x216d, 0x2174, 0x2170, 0x2173, 0x2175, 0x216c,
	0x214a, 0x214b, 0x2176, 0x215c, 0x2124, 0x215d, 0x2125, 0x213f,
	0x2330, 0x2331, 0x2332, 0x2333, 0x2334, 0x2335, 0x2336, 0x2337,
	0x2338, 0x2339, 0x2127, 0x2128, 0x2152, 0x2161, 0x2153, 0x2129,
	0x2177, 0x2341, 0x2342, 0x2343, 0x2344, 0x2345, 0x2346, 0x2347,
	0x2348, 0x2349, 0x234a, 0x234b, 0x234c, 0x234d, 0x234e, 0x234f,
	0x2350, 0x2351, 0x2352, 0x2353, 0x2354, 0x2355, 0x2356, 0x2357,
	0x2358, 0x2359, 0x235a, 0x214e, 0x2140, 0x214f, 0x2130, 0x2132,
	0x212e, 0x2361, 0x2362, 0x2363, 0x2364, 0x2365, 0x2366, 0x2367,
	0x2368, 0x2369, 0x236a, 0x236b, 0x236c, 0x236d, 0x236e, 0x236f,
	0x2370, 0x2371, 0x2372, 0x2373, 0x2374, 0x2375, 0x2376, 0x2377,
	0x2378, 0x2379, 0x237a, 0x2150, 0x2143, 0x2151, 0x2141, 0x2121,
};

static void
initrevbitmap()
{
	int i;
	int j;
	u_char x;
	int right;
	int left;

	revbitmap = (u_char *)malloc(256 * sizeof(u_char));
	leftbitmap= (char *)malloc(256 * sizeof(char));
	rightbitmap= (char *)malloc(256 * sizeof(char));
	if (!revbitmap || !leftbitmap || !rightbitmap) {
		fprintf(stderr, "initrevbitmap: malloc failed\n");
		cleanup(-1);
	}
	for (i = 0; i < 256; i++) {
		right = left = -1;
		x = 0x00;
		for (j = 0; j < 8; j++) {
			if (i & (0x80 >> j)) {
				x |= (1 << j);
				right = j;
				if (left == -1)
					left = j;
			}
		}
		revbitmap[i] = x;
		rightbitmap[i] = right;
		leftbitmap[i] = left;
	}
}

#define	VFC_INSHASH(vfc) {						\
	struct vfont *h, *n;						\
	h = &vfcache[VFCACHE_HASH(vfc->code, vfc->width, vfc->height)];	\
	n = h->next; vfc->next = n; n->prev = vfc;			\
	h->next = vfc; vfc->prev = h;					\
}
#define	VFC_DELHASH(vfc) {						\
	struct vfont *n, *p;						\
	n = vfc->next; p = vfc->prev;					\
	n->prev = p; p->next = n;					\
}
#define	VFC_INSLRU(vfc) {						\
	struct vfont *p;						\
	p = vfclru.lruprev; vfc->lruprev = p; p->lrunext = vfc;		\
	vfclru.lruprev = vfc; vfc->lrunext = &vfclru;			\
}
#define	VFC_DELLRU(vfc) {						\
	struct vfont *n, *p;						\
	n = vfc->lrunext; p = vfc->lruprev;				\
	n->lruprev = p; p->lrunext = n;					\
}

static void
vfc_init()
{
	int i;
	struct vfont *vfc;

	vfc = vfcache;
	for (vfc = vfcache, i = 0; i < VFHASH_SIZE; vfc++, i++)
		vfc->next = vfc->prev = vfc;
	vfclru.lrunext = vfclru.lruprev = &vfclru;
	vfcinitdone++;
}

static void
vfc_free(vfc)
	struct vfont *vfc;
{
	VFC_DELHASH(vfc);
	VFC_DELLRU(vfc);
	free(vfc->dbitmap);
	free(vfc);
	vfcachecnt--;
}

static struct vfont *
vfc_lookup(code, width, height, fontname)
	u_int code, width, height;
	char *fontname;
{
	struct vfont *vfc, *hvfc;

	hvfc = &vfcache[VFCACHE_HASH(code, width, height)];
	for (vfc = hvfc->next; vfc != hvfc; vfc = vfc->next) {
		if (vfc->code == code &&
		    vfc->width == width &&
		    vfc->height == height &&
		    vfc->fontname == fontname) {
			vfcachehit++;
			VFC_DELLRU(vfc);
			VFC_INSLRU(vfc);
			return vfc;
		}
	}
	vfcachemiss++;
	return NULL;
}

static struct vfont *
vfc_alloc(code, width, height, fontname)
	u_int code, width, height;
	char *fontname;
{
	u_int rast = (width + 7) / 8;
	u_int rast2 = (width * 2 + 7) / 8;
	u_char *lbitmap;
	u_char *dbitmap;
	u_int char_len;
	struct vfont *vfc;
	u_int max_x, min_x;
	u_int ix, iy;
	u_char data, *bitmap;
	u_char *s;
	u_int bsize;
	u_int vcode;
	XImage *xim;

	bsize = rast * height;
	if (code < 128)
		vcode = ctable[code];
	else
		vcode = code;
	vfc = (struct vfont *)malloc(sizeof(*vfc));
	if (vfc == NULL) {
		fprintf(stderr, "vfc_alloc: malloc failed\n");
		cleanup(-1);
	}
	vfc->code = code;
	vfc->width = width;
	vfc->height = height;
	vfc->fontname = vf_curname;
	vfc->ref = 0;
	bitmap = (u_char *)malloc(bsize);
	if (bitmap == NULL) {
		fprintf(stderr, "vfc_alloc: malloc failed\n");
		cleanup(-1);
	}
#if 1	/* ANTIALIAS */
/*XXX: TODO: cleanup, optimization */
	lbitmap = (u_char *)malloc(rast2 * height * 2);
	dbitmap = (u_char *)malloc(width * height);
	if (lbitmap == NULL || dbitmap == NULL) {
		fprintf(stderr, "vfc_alloc: malloc failed\n");
		cleanup(-1);
	}
	memset(lbitmap, 0, rast2 * height * 2);
	VF_GetBitmap(vcode, vfont_fd, width * 2, height * 2, rast2, 0, lbitmap);

    {
	int i, j, x1, x2, y, b1, b2;
	u_char *s1, *s2, *d, *z;
	static u_char dp[] = { 0, 1, 1, 2 };

	z = dbitmap;
	d = bitmap;
	for (j = 0; j < height; j++) {
		s1 = lbitmap + rast2 * (j * 2);
		s2 = s1 + rast2;
		y = 0;
		for (i = 0, b1 = 7, b2 = 6; i < width; i++, b1--, b2 -= 2) {
			if (b1 < 0) {
				*d++ = y;
				y = 0;
				b1 = 7;
			}
			if (b2 < 0) {
				s1++, s2++;
				b2 = 6;
			}
			x1 = (*s1 >> b2) & 0x03;
			x2 = (*s2 >> b2) & 0x03;
			if (x1 | x2)
				y |= 1 << b1;
			*z++ = dp[x1] + dp[x2];
		}
		*d++ = y;
		for (i += 2; i < rast * 2; i++)
			*d++ = 0;
	}
    }
	free(lbitmap);
	vfc->dbitmap = dbitmap;
#else
	memset(bitmap, 0, bsize);
	if (!(mgp_flag & FL_OUTLINE)) {
		VF_GetBitmap(vcode, vfont_fd, width, height, rast, 0, bitmap);
	} else {  /* OR */
		u_long *vfdata;

		if ((vfdata = VF_GetOutline(vcode, vfont_fd)) == NULL) {
			fprintf(stderr, "Failed to get outline : %04x\n", vcode);
		} else {
			VF_DrawOutline(vfdata, vfont_fd, width, height, rast,
				0, bitmap);
			VF_FreeOutline(vfdata, vfont_fd);
		}
	}
#endif /*ANTIALIAS*/

	if (!revbitmap)
		initrevbitmap();

	/* compute right/left boundary */
	max_x = -1;
	min_x = -1;
	for (ix = 0; ix < rast; ix++) {
		data = 0x00;
		s = bitmap + ix;
		for (iy = 0, s = bitmap + ix; iy < height; iy++, s += rast)
			data |= *s;
		if (data) {
			max_x = ix * 8 + rightbitmap[data];
			if (min_x == -1)
				min_x = ix * 8 + leftbitmap[data];
		}
	}
	/* XXX: ".," ... */
	if (code >= 0x2121 && code <= 0x2125 && max_x < width / 2)
		max_x += width / 2;

#if 0  /* remove soon */
	xim = XCreateImage(display, visual, 1,
		XYBitmap, 0, NULL, width, height, 8, 0);
	if (xim == NULL) {
		fprintf(stderr, "vfc_alloc: XCreateImage failed\n");
		cleanup(-1);
	}
	if (xim->bitmap_bit_order == LSBFirst) {
		/* convert the bit order of image bitmap */
		for (iy = 0, s = bitmap; iy < height; iy++) {
			for (ix = 0; ix < rast; ix++, s++)
				*s = revbitmap[*s];
		}
	}
	xim->data = (char *)bitmap;
	vfc->image = xim;
#endif
	/* horizontal gap of 1 dot is included by default */
	char_len = max_x - min_x + 2;
	vfc->xoff = min_x;
	vfc->charlen = vfc->xmax = char_len;
	vfc->descent = height / VF_DESCENT;
	vfc->ascent = vfc->height - vfc->descent;

	VFC_INSHASH(vfc);
	VFC_INSLRU(vfc);
	vfcachecnt++;
	return vfc;
}

struct vfont *
vfc_get(code, width, height, force)
	u_int code, width, height;
	int force;
{
	struct vfont *vfc, *nvfc;

	/* sanity check */
	if (vf_curname == NULL)
		return NULL;

	if (!vfcinitdone)
		vfc_init();
	vfc = vfc_lookup(code, width, height, vf_curname);
	if (vfc == NULL) {
		if (vfcachecnt >= vfcachesize) {
			if (!force)
				return NULL;
			vfc = vfclru.lrunext;
			while (vfcachecnt >= vfcachesize) {
				if (vfc == &vfclru)
					break;
				nvfc = vfc->lrunext;
				if (vfc->ref == 0)
					vfc_free(vfc);
				vfc = nvfc;
			}
		}
		vfc = vfc_alloc(code, width, height, vf_curname);
	}
#if 0
	fprintf(stderr, "vfc_get: cache=%d, hit=%d, miss=%d\n",
		vfcachecnt, vfcachehit, vfcachemiss);
#endif
	return vfc;
}

void
vfc_setfont(fontname)
	char *fontname;
{
	static char *prev_vfcap;

	if ((mgp_flag & FL_NOVFLIB)) {
		return;
	}

	if (prev_vfcap != vfcap_name) {
		/*
		 * Because the string specified by %vfcap is stored in
		 * allocated area and never freed, string compare is not
		 * needed here.
		 */
		if (prev_vfcap)
			VF_Deinit();
		if (VF_Init(vfcap_name) < 0) {
			fprintf(stderr, "Error VF_Init\n");
			cleanup(-1);
		}
		prev_vfcap = vfcap_name;
		if (prev_vfcap == NULL)
			prev_vfcap = "";	/* XXX: just in case */
	}

#if 0
	if (vfont_fd >= 0)
		VF_CloseFont(vfont_fd);
#endif 

	vfont_fd = VF_OpenFont(fontname);
	vf_curname = fontname;
	if (vfont_fd < 0) {
		fprintf(stderr, "Can't open vffont %s (vfcap=%s)\n",
			fontname, vfcap_name);
		cleanup(-1);
	}
}

XImage *
vfc_image(vfc, fore, back, xim, bx, by)
	struct vfont *vfc;
	long fore, back;
	XImage *xim;
	u_int bx, by;
{
	int x, y;
	int maxx;
	static XColor col[5];
	u_char d, *s;
	u_long p;
	XColor *bcol = NULL, *bc;
	u_int charlen;

	/* offset the base position */
	by -= vfc->ascent;

	charlen = vfc->charlen;
	if (vfc->xoff + charlen > vfc->width)
		charlen = vfc->width - vfc->xoff;

	if (back == -1) {
		u_long r, g, b;
		int dots;

		dots = charlen * vfc->height;
		bcol = malloc(sizeof(XColor) * dots);
		if (bcol == NULL)
			return NULL;
		bc = bcol;
		for (y = 0; y < vfc->height; y++) {
			for (x = 0; x < charlen; x++, bc++) {
				bc->pixel = XGetPixel(xim, bx+x, by+y);
				bc->flags = DoRed|DoGreen|DoBlue;
			}
		}
		XQueryColors(display, colormap, bcol, dots);
		r = g = b = 0;
		for (y = 0; y < vfc->height; y++) {
			for (x = 0; x < charlen; x++) {
				r += bcol[x].red;
				g += bcol[x].green;
				b += bcol[x].blue;
			}
		}
		r /= dots; g /= dots; b /= dots;
		bc = &col[0];
		if (bc->red == r && bc->green == g && bc->blue == b)
			bc->pixel = back;
		else {
			bc->pixel = ~back;
			bc->red   = r; bc->green = g; bc->blue  = b;
		}
	}
	if (fore != col[4].pixel || back != col[0].pixel) {
		col[4].pixel = fore;
		col[4].flags = DoRed|DoGreen|DoBlue;
		if (back != -1) {
			col[3].pixel = back;
			col[3].flags = DoRed|DoGreen|DoBlue;
			XQueryColors(display, colormap, &col[3], 2);
			col[0] = col[3];
		} else {
			XQueryColor(display, colormap, &col[4]);
		}
		for (x = 3; x > 0; x--) {
			col[x].red   = (col[4].red  *x + col[0].red  *(4-x)) /4;
			col[x].green = (col[4].green*x + col[0].green*(4-x)) /4;
			col[x].blue  = (col[4].blue *x + col[0].blue *(4-x)) /4;
			if (!XAllocColor(display, colormap, &col[x])) {
				if (verbose)
					printf("vfc_image: cannot allocate color for level %d (using %d)\n", x, x + 1);
				col[x].pixel = col[x + 1].pixel;
			}
			else regist_alloc_colors(&font_clr, &col[x].pixel, 1);
		}
	}

	/* XXX: need optimization */
	s = vfc->dbitmap;
	bc = bcol;
	maxx = bx + charlen;
	for (y = by; y < vfc->height + by; y++) {
		s += vfc->xoff;
		for (x = bx; x < maxx; x++) {
			if ((d = *s++)) {
				p = col[d].pixel;
				XPutPixel(xim, x, y, p);
			}
		}
		s += vfc->width - (vfc->xoff + charlen);
	}
	if (mgp_flag & FL_GLYPHEDGE) {
		/*
		 * for debugging treatment of font metrics, for 16bpp displays
		 */
		/* pixmap bounding box */
		for (y = 0; y < vfc->height; y++) {
			XPutPixel(xim, bx, by + y, 0xffff);
			XPutPixel(xim, bx + charlen - 1, by + y, 0xffff);
		}
		for (x = 0; x < charlen; x++) {
			XPutPixel(xim, bx + x, by, 0xffff);
			XPutPixel(xim, bx + x, by + vfc->height - 1, 0xffff);
		}
		/* origin */
		XPutPixel(xim, bx, by, 0xaaaa);
		/* baseline */
		for (x = 0; x < vfc->width; x++)
			XPutPixel(xim, bx + vfc->xoff + x, by, 0xaaaa);
	}
	if (bcol)
		free(bcol);
	return xim;
}

#endif /* VFLIB */
