/* aafont.c - generic library for drawing anti-aliased fonts
   Copyright (C) 1996-2000 Paul Sheer

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 2 of the License, or
   (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
   02111-1307, USA.
 */

#include <config.h>
#include <stdlib.h>
#include <stdio.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include "aafont.h"
#include "mad.h"



/*
 * list management
 * ---------------
 *
 * Situation is a list of aa fonts:
 * Each font is uniquified by a fg colour, a bg colour and a fontid.
 * Each font has 65536 pixmap glyphs.
 * The 65536 pixmaps are divided into 256 blocks.
 * Each block is allocated on demand only.
 * We *ImageString* only i.e. no DrawString - since requires knowledge
 *                             of the background which is complicated.
 */

Display *aa_display;
int aa_depth;
Window aa_root;
Visual *aa_visual;

int option_rgb_order = RedFirst;
int option_interchar_spacing = 0;

void XAaInit (Display * display, Visual * visual, int depth, Window root)
{
    aa_display = display;
    aa_depth = depth;
    aa_root = root;
    aa_visual = visual;
}

struct aa_glyph_cache {
    Pixmap pixmap;
    int width;
};

struct aa_font_cache {
    XFontStruct *font_struct;
    GC gc;
    unsigned long fg;
    unsigned long bg;
    struct aa_glyph_cache *glyph[256];
    int num_pixmaps;
    struct aa_font_cache *next;
} *font_cache_list = 0;

static void aa_insert (void)
{
    struct aa_font_cache *p;
    p = malloc (sizeof (*font_cache_list));
    memset (p, 0, sizeof (*font_cache_list));
    if (!font_cache_list) {
	font_cache_list = p;
    } else {
	p->next = font_cache_list;
	font_cache_list = p;
    }
}

static void aa_free (struct aa_font_cache *f)
{
    int i, j;
    XFreeFontInfo (0, f->font_struct, 0);
    for (i = 0; i < 256; i++) {
	if (f->glyph[i]) {
	    for (j = 0; j < 256; j++)
		if (f->glyph[i][j].pixmap)
		    XFreePixmap (aa_display, f->glyph[i][j].pixmap);
	    memset (f->glyph[i], 0, 256 * sizeof (struct aa_glyph_cache));
	    free (f->glyph[i]);
	}
    }
    memset (f, 0, sizeof (*f));
    free (f);
}

/* passing fg == bg == 0 finds any fid */
static struct aa_font_cache *aa_find (Font fid, unsigned long fg, unsigned long bg)
{
    struct aa_font_cache *p;
    for (p = font_cache_list; p; p = p->next)
	if (fid && p->font_struct->fid == fid && p->fg == fg && p->bg == bg)
	    return p;
    return 0;
}

/* returns zero on not found */
static int _aa_remove (Font fid)
{
    struct aa_font_cache *p, *q = 0;
    for (p = font_cache_list; p; p = p->next) {
	if (fid && p->font_struct->fid == fid) {
	    if (p == font_cache_list) {
		struct aa_font_cache *t;
		t = font_cache_list->next;
		aa_free (font_cache_list);
		font_cache_list = t;
		return 1;
	    } else {
		q->next = p->next;
		aa_free (p);
		return 1;
	    }
	}
	q = p;
    }
    return 0;
}

static void aa_remove (Font fid)
{
    while (_aa_remove (fid));
}


/* fifth level */
/* 5 by 9/3 guassian convolution */
static unsigned long aa_convolve (int i, int j, unsigned char *source, int source_bytes_per_line,
				  int byte_order, int bytes_per_pixel, int rgb_order, int red_shift,
				  int green_shift, int blue_shift, int red_mask, int green_mask,
				  int blue_mask)
{
    unsigned long red, green, blue;
#include "conv.c"
    red /= (256 * 3);
    green /= (256 * 3);
    blue /= (256 * 3);
    return (red << red_shift) | (green << green_shift) | (blue << blue_shift);
}

/* fourth level */
static Pixmap aa_shrink_pixmap (struct aa_font_cache *f, Pixmap pixmap, int width, int height,
				int *width_return)
{
    XImage *image, *shrunk;
    int i, j, w, h, bytes_per_pixel;
    int red_shift, green_shift, blue_shift;
    unsigned long red_mask, green_mask, blue_mask;

/* create an image to put the enlarged glyph into - make it slightly large to hold
   the diameter of the 5x9 guassian as well as a one pixel enlargement and rounding error */
    image =
	XCreateImage (aa_display, aa_visual, aa_depth, ZPixmap, 0, 0, width + 4 + X_ENLARGEMENT + option_interchar_spacing * 3,
		      height + 8 + Y_ENLARGEMENT, 8, 0);
    bytes_per_pixel = image->bytes_per_line / image->width;
    image->data = (char *) malloc (image->bytes_per_line * image->height);

    for (i = 0; i < width + 4 + X_ENLARGEMENT + option_interchar_spacing; i++)
	XPutPixel (image, i, 0, f->bg);
    for (j = 0; j < height + 8 + Y_ENLARGEMENT; j++)
	memcpy (image->data + image->bytes_per_line * j, image->data, image->bytes_per_line);

/* create an image to put the reduced glyph into. round w and h up */
    *width_return = w = SHRINK_WIDTH (width);
    h = SHRINK_HEIGHT (height);
    shrunk = XCreateImage (aa_display, aa_visual, aa_depth, ZPixmap, 0, 0, w, h, 8, 0);
    shrunk->data = (char *) malloc (shrunk->bytes_per_line * h);

    for (red_mask = image->red_mask, red_shift = 0; red_shift < 32 && !(red_mask & 1);
	 red_shift++, red_mask >>= 1);
    for (green_mask = image->green_mask, green_shift = 0; green_shift < 32 && !(green_mask & 1);
	 green_shift++, green_mask >>= 1);
    for (blue_mask = image->blue_mask, blue_shift = 0; blue_shift < 32 && !(blue_mask & 1);
	 blue_shift++, blue_mask >>= 1);

    XGetSubImage (aa_display, pixmap, 0, 0, width, height,
		  image->red_mask | image->green_mask | image->blue_mask, ZPixmap, image, 2, 4);

    for (i = 0; i < w; i++) {
	for (j = 0; j < h; j++) {
	    unsigned long pixel;
	    pixel =
		aa_convolve (i * 3, j * 3,
			     (unsigned char *) image->data + bytes_per_pixel * 2 +
			     image->bytes_per_line * 4, image->bytes_per_line, image->byte_order,
			     bytes_per_pixel, option_rgb_order, red_shift, green_shift, blue_shift,
			     red_mask, green_mask, blue_mask);
	    XPutPixel (shrunk, i, j, pixel);
	}
    }

    pixmap = XCreatePixmap (aa_display, aa_root, w, h, aa_depth);
    XPutImage (aa_display, pixmap, f->gc, shrunk, 0, 0, 0, 0, w, h);
    free (image->data);
    image->data = 0;
    XDestroyImage (image);
    free (shrunk->data);
    shrunk->data = 0;
    XDestroyImage (shrunk);
    return pixmap;
}

/* third level */
static Pixmap aa_create_pixmap (struct aa_font_cache *f, int j, int i, int *width)
{
    Pixmap w, r;
    int direction, ascent, descent, height;
    XCharStruct ch;
    XChar2b c;
    c.byte1 = j;
    c.byte2 = i;
    XTextExtents16 (f->font_struct, &c, 1, &direction, &ascent, &descent, &ch);
    height = f->font_struct->ascent + f->font_struct->descent;
    w = XCreatePixmap (aa_display, aa_root, ch.width, height, aa_depth);
/* cheapest way to clear the background */
    XDrawImageString (aa_display, w, f->gc, 0, f->font_struct->ascent, "     ", 5);
/* needed to clear the background if the function fails on non-existing chars */
    XDrawImageString16 (aa_display, w, f->gc, 0, f->font_struct->ascent, &c, 1);
    r = aa_shrink_pixmap (f, w, ch.width, height, width);
    XFreePixmap (aa_display, w);
    return r;
}

/* second level */
static void aa_create_pixmap_ (struct aa_font_cache *f, int j, int i)
{
    if (!f->glyph[j]) {
	f->glyph[j] = malloc (sizeof (struct aa_glyph_cache) * 256);
	memset (f->glyph[j], 0, sizeof (struct aa_glyph_cache) * 256);
    }
    if (!f->glyph[j][i].pixmap)
	f->glyph[j][i].pixmap = aa_create_pixmap (f, j, i, &f->glyph[j][i].width);
}

/* top level */
static void aa_create_pixmaps (struct aa_font_cache *f, XChar2b * wc, unsigned char *c, int n)
{
    int i;
    if (aa_visual->class != TrueColor) {
	fprintf
	    (stderr,
	     "%s:%d: Can't do anti-aliasing without TrueColor visual.\nTry setting your X server to non-8-bits-per-pixel display.\n",
	     __FILE__, __LINE__);
	exit (1);
    }
    if (wc) {
	for (i = 0; i < n; i++)
	    aa_create_pixmap_ (f, wc[i].byte1, wc[i].byte2);
    } else {
	for (i = 0; i < n; i++)
	    aa_create_pixmap_ (f, 0, c[i]);
    }
}

int _XAaDrawImageStringWC (Display * display, Drawable d, GC gc, int x, int y, char *s,
			   XChar2b * wc, int length)
{
    int i, x_start = x;
    struct aa_font_cache *f;
    XGCValues values_return;
    XGetGCValues (display, gc, GCForeground | GCBackground | GCFont, &values_return);
    f = aa_find (values_return.font, values_return.foreground, values_return.background);
    if (!f) {
	aa_insert ();
	f = font_cache_list;
	f->font_struct = XQueryFont (display, values_return.font);
	f->gc = gc;
	f->fg = values_return.foreground;
	f->bg = values_return.background;
	aa_display = display;
    }
    aa_create_pixmaps (f, wc, (unsigned char *) s, length);
    if (wc) {
	for (i = 0; i < length; i++) {
	    int width = f->glyph[wc[i].byte1][wc[i].byte2].width;
	    int height = SHRINK_HEIGHT (f->font_struct->ascent + f->font_struct->descent);
	    XCopyArea (display, f->glyph[wc[i].byte1][wc[i].byte2].pixmap, d, gc, 0, 0, width,
		       height, x, y - f->font_struct->ascent / 3);
	    x += width;
	}
    } else {
	for (i = 0; i < length; i++) {
	    int width = f->glyph[0][(unsigned char) s[i]].width;
	    int height = SHRINK_HEIGHT (f->font_struct->ascent + f->font_struct->descent);
	    XCopyArea (display, f->glyph[0][(unsigned char) s[i]].pixmap, d, gc, 0, 0, width,
		       height, x, y - f->font_struct->ascent / 3);
	    x += width;
	}
    }
    return x - x_start;
}

int XAaTextWidth (XFontStruct * font_struct, char *s, int length)
{
    int w = 0, i;
    int direction, ascent, descent;
    for (i = 0; i < length; i++) {
	XCharStruct ch;
	XTextExtents (font_struct, s + i, 1, &direction, &ascent, &descent, &ch);
	w += SHRINK_WIDTH (ch.width);
    }
    return w;
}

int XAaTextWidth16 (XFontStruct * font_struct, XChar2b * s, int length)
{
    int w = 0, i;
    int direction, ascent, descent;
    for (i = 0; i < length; i++) {
	XCharStruct ch;
	XTextExtents16 (font_struct, s + i, 1, &direction, &ascent, &descent, &ch);
	w += SHRINK_WIDTH (ch.width);
    }
    return w;
}

int XAaDrawImageString16 (Display * display, Drawable d, GC gc, int x, int y, XChar2b * wc,
			  int length)
{
    return _XAaDrawImageStringWC (display, d, gc, x, y, 0, wc, length);
}

int XAaDrawImageString (Display * display, Drawable d, GC gc, int x, int y, char *s, int length)
{
    return _XAaDrawImageStringWC (display, d, gc, x, y, s, 0, length);
}

void XAaFree (Font fid)
{
    aa_remove (fid);
}

