/* font.h - multiple font handling
   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 "coolwidget.h"
#include "coollocal.h"
#include "aafont.h"

#include "mad.h"

#define TEST_FONT_STRING "The Quick Brown Fox Jumps Over The Lazy Dog"

int option_no_font_set = 0;

static struct font_stack {
    struct font_object *f;
    struct font_stack *next;
} *font_stack = 0;

/* only ImageStrings can bee anti-aliased */
int CImageTextWidth (const char *s, int l)
{
    if (FONT_USE_FONT_SET)
	return XmbTextEscapement (current_font->font_set, s, l);
    if (FONT_ANTIALIASING)
	return XAaTextWidth (current_font->font_struct, (char *) s, l);
    return XTextWidth (current_font->font_struct, s, l);
}

static XChar2b *wchar_t_to_XChar2b (wchar_t * swc, int l)
{
    XChar2b *p, *s;
    int r = l;
    p = s = malloc (l * sizeof (XChar2b));
    while (r--) {
	p->byte1 = (unsigned char) (*swc >> 8);
	(p++)->byte2 = (unsigned char) (*(swc++) & 0xFF);
    }
    return s;
}

int CImageTextWidthWC (XChar2b * s, wchar_t * swc, int l)
{
    if (FONT_USE_FONT_SET)
	return XwcTextEscapement (current_font->font_set, swc, l);
    if (!s) {
	int r;
	if (FONT_ANTIALIASING)
	    r = XAaTextWidth16 (current_font->font_struct, s = wchar_t_to_XChar2b (swc, l), l);
	else
	    r = XTextWidth16 (current_font->font_struct, s = wchar_t_to_XChar2b (swc, l), l);
	free (s);
	return r;
    }
    if (FONT_ANTIALIASING)
	return XAaTextWidth16 (current_font->font_struct, s, l);
    return XTextWidth16 (current_font->font_struct, s, l);
}

int CImageStringWidth (const char *s)
{
    return CImageTextWidth (s, strlen (s));
}

int CImageText (Window w, int x, int y, const char *s, int l)
{
    if (FONT_USE_FONT_SET) {
	XmbDrawImageString (CDisplay, w, current_font->font_set, CGC, x, y, s, l);
	return XmbTextEscapement (current_font->font_set, s, l);
    }
    if (FONT_ANTIALIASING)
	return XAaDrawImageString (CDisplay, w, CGC, x, y, (char *) s, l);
    return XDrawImageString (CDisplay, w, CGC, x, y, s, l);
}

int CText (Window w, int x, int y, const char *s, int l)
{
    if (FONT_USE_FONT_SET) {
	XmbDrawString (CDisplay, w, current_font->font_set, CGC, x, y, s, l);
	return XmbTextEscapement (current_font->font_set, s, l);
    }
    return XDrawString (CDisplay, w, CGC, x, y, s, l);
}

int CImageTextWC (Window w, int x, int y, XChar2b * s, wchar_t * swc, int l)
{
    if (FONT_USE_FONT_SET) {
	XwcDrawImageString (CDisplay, w, current_font->font_set, CGC, x, y, swc, l);
	return XwcTextEscapement (current_font->font_set, swc, l);
    }
    if (!s) {
	int r;
	if (FONT_ANTIALIASING)
	    r = XAaDrawImageString16 (CDisplay, w, CGC, x, y, s = wchar_t_to_XChar2b (swc, l), l);
	else
	    r = XDrawImageString16 (CDisplay, w, CGC, x, y, s = wchar_t_to_XChar2b (swc, l), l);
	free (s);
	return r;
    }
    if (FONT_ANTIALIASING)
	return XAaDrawImageString16 (CDisplay, w, CGC, x, y, s, l);
    return XDrawImageString16 (CDisplay, w, CGC, x, y, s, l);
}

int CImageString (Window w, int x, int y, const char *s)
{
    return CImageText (w, x, y, s, strlen (s));
}

static struct font_object *find_font (char *name)
{
    struct font_object *n;
    if (current_font)
	if (!strcmp (current_font->name, name))
	    return current_font;
    for (n = all_fonts; n; n = n->next)
	if (!strcmp (n->name, name))
	    return n;
    return 0;
}

/*
 *     ●    If the min_byte1 and max_byte1 members are both zero,
 *           min_char_or_byte2 specifies the linear character
 *          index corresponding to the first element of the
 *           per_char array, and max_char_or_byte2 specifies the
 *          linear character index of the last element.
 *
 *           If either min_byte1 or max_byte1 are nonzero, both
 *          min_char_or_byte2 and max_char_or_byte2 are less than
 *           256, and the 2-byte character index values corre­
 *          sponding to the per_char array element N (counting
 *           from 0) are:
 *
 *               byte1 = N/D + min_byte1
 *               byte2 = N%D + min_char_or_byte2
 *
 *          where:
 *
 *                   D = max_char_or_byte2 - min_char_or_byte2 + 1
 *                   / = integer division
 *                   % = integer modulus
 */


/* returns width. the descent is only ever used for drawing an underbar.
   the ascent is only ever used in calculating FONT_PIX_PER_LINE */
static int get_wchar_dimension (wchar_t ch, int *height, int *ascent, int *ink_descent)
{
    int width, direction;
    XRectangle ink;
    XRectangle logical;

    if (FONT_USE_FONT_SET) {
	width = XwcTextExtents (current_font->font_set, &ch, 1, &ink, &logical);
	if (height)
	    *height = logical.height;
	if (ascent)
	    *ascent = (-logical.y);
	if (ink_descent)
	    *ink_descent = ink.height + ink.y;
    } else {
	XCharStruct c;
	XChar2b s;
	int ascent_, descent, w;
	s.byte2 = ch & 0xFF;
	s.byte1 = (ch >> 8) & 0xFF;
	XTextExtents16 (current_font->font_struct, &s, 1, &direction, &ascent_, &descent, &c);
	width = c.width;
	if (FONT_ANTIALIASING) {
	    width = SHRINK_WIDTH (width);
	    if (ascent)
		*ascent = ascent_ / 3;
	    if (height)
		*height = SHRINK_HEIGHT (ascent_ + descent);
	} else {
	    if (ascent)
		*ascent = ascent_;
	    if (height)
		*height = ascent_ + descent;
	}
	w =
	    current_font->font_struct->max_char_or_byte2 -
	    current_font->font_struct->min_char_or_byte2 + 1;
	if (w == 1)
	    w = 0;
	if (ink_descent) {
	    if (s.byte2 >= current_font->font_struct->min_char_or_byte2
		&& s.byte2 <= current_font->font_struct->max_char_or_byte2
		&& s.byte1 >= current_font->font_struct->min_byte1
		&& s.byte1 <= current_font->font_struct->max_byte1) {
		if (current_font->font_struct->per_char) {
		    *ink_descent =
			current_font->font_struct->per_char[
							    (s.byte2 -
							     current_font->font_struct->
							     min_char_or_byte2) + w * (s.byte1 -
										       current_font->
										       font_struct->
										       min_byte1)].
			descent;
		} else {
/* this happens when you try to scale a non-scalable font */
		    *ink_descent = current_font->font_struct->max_bounds.descent;
		}
	    } else {
		*ink_descent = 0;
	    }
	    if (FONT_ANTIALIASING)
		*ink_descent = (*ink_descent + 3) / 3;
	}
    }
    return width;
}

/* returns width. the descent is only ever used for drawing an underbar.
   the ascent is only ever used in calculating FONT_PIX_PER_LINE */
static int get_string_dimensions (char *s, int n, int *height, int *ascent, int *ink_descent)
{
    int width, direction;
    XRectangle ink;
    XRectangle logical;
    if (FONT_USE_FONT_SET) {
	width = XmbTextExtents (current_font->font_set, s, n, &ink, &logical);
	if (height)
	    *height = logical.height;
	if (ascent)
	    *ascent = (-logical.y);
	if (ink_descent)
	    *ink_descent = ink.height + ink.y;
    } else {
	XCharStruct c;
	int ascent_, descent;
	XTextExtents (current_font->font_struct, s, n, &direction, &ascent_, &descent, &c);
	if (FONT_ANTIALIASING) {
	    width = SHRINK_WIDTH (c.width);
	    if (ascent)
		*ascent = ascent_ / 3;
	    if (height)
		*height = SHRINK_HEIGHT (ascent_ + descent);
	} else {
	    width = c.width;
	    if (ascent)
		*ascent = ascent_;
	    if (height)
		*height = ascent_ + descent;
	}
	if (ink_descent) {
	    if (n == 1) {
		int i;
		i = (unsigned char) *s;
		if (i >= current_font->font_struct->min_char_or_byte2
		    && i <= current_font->font_struct->max_char_or_byte2)
			*ink_descent =
			current_font->font_struct->per_char[i -
							    current_font->
							    font_struct->min_char_or_byte2].descent;
		else
		    *ink_descent = 0;
	    } else
		*ink_descent = descent;
	    if (FONT_ANTIALIASING)
		*ink_descent = (*ink_descent + 3) / 3;
	}
#if 0
	width = CTextWidth (s, n);
#endif
    }
    return width;
}

static int check_font_fixed (void)
{
    int m;
    char *p;
    m = get_string_dimensions ("M", 1, 0, 0, 0);
    for (p = "!MI .1@~"; *p; p++)
	if (m != get_string_dimensions (p, 1, 0, 0, 0))
	    return 0;
    return m;
}

static void get_font_dimensions (void)
{
    unsigned char *p, q[256];
    int i;

/* fill an array with graphical characters of the current locale */
    for (p = q, i = 1; i < 255; i++)
	if (isgraph ((unsigned char) i))
	    *p++ = (unsigned char) i;
    *p = '\0';

/* get the dimensions of the complete character set */
    get_string_dimensions ((char *) q, i, &FONT_HEIGHT, &FONT_ASCENT, &FONT_DESCENT);

/* create an example sentance */
    p = (unsigned char *) TEST_FONT_STRING;

/* get the mean font width from the example sentance */
    FONT_MEAN_WIDTH =
	get_string_dimensions ((char *) p, strlen ((char *) p), 0, 0, 0) / strlen ((char *) p);

    for (i = 255; i >= 0; i--)
	current_font->per_char_256[i] = FONT_PER_CHAR (i);
}

/* this tries to keep the array of cached of font widths as small ever
   needed - i.e. only enlarges on lookups */
static void _font_per_char (wchar_t c)
{
    if (!current_font->per_char) {
	current_font->num_per_char = c + 1;
	current_font->per_char = CMalloc (current_font->num_per_char * sizeof (fontdim_t));
	memset (current_font->per_char, 0xFF, current_font->num_per_char * sizeof (fontdim_t));
    } else if (c >= current_font->num_per_char) {
	long l;
	fontdim_t *t;
	l = c + 256;
	t = CMalloc (l * sizeof (fontdim_t));
	memcpy (t, current_font->per_char, current_font->num_per_char * sizeof (fontdim_t));
	memset (t + current_font->num_per_char, 0xFF,
		(l - current_font->num_per_char) * sizeof (fontdim_t));
	free (current_font->per_char);
	current_font->per_char = t;
	current_font->num_per_char = l;
    }
    if (current_font->per_char[c].width == 0xFF) {
	int d;
	current_font->per_char[c].width = get_wchar_dimension (c, 0, 0, &d);
	current_font->per_char[c].descent = (unsigned char) d;
	if (FIXED_FONT)
	    if ((current_font->per_char[c].width != FIXED_FONT) && current_font->per_char[c].width)
		FIXED_FONT = 0;
    }
}

int font_per_char (wchar_t c)
{
    if ((unsigned long) c > FONT_LAST_UNICHAR)
	return 0;
    _font_per_char (c);
    return current_font->per_char[c].width;
}

int font_per_char_descent (wchar_t c)
{
    if ((unsigned long) c > FONT_LAST_UNICHAR)
	return 0;
    _font_per_char (c);
    return current_font->per_char[c].descent;
}

/* seems you cannot draw different fonts in the same GC. this is
   strange???, so we create a dummy GC for each font */
static Window get_dummy_gc (void)
{
    static Window dummy_window = 0;
    XGCValues gcv;
    if (!dummy_window) {
	XSetWindowAttributes xswa;
	xswa.override_redirect = 1;
	dummy_window =
	    XCreateWindow (CDisplay, CRoot, 0, 0, 1, 1, 0, CDepth, InputOutput, CVisual,
			   CWOverrideRedirect, &xswa);
    }
    gcv.foreground = COLOR_BLACK;
    if (current_font->font_struct) {
	gcv.font = current_font->font_struct->fid;
	CGC = XCreateGC (CDisplay, dummy_window, GCForeground | GCFont, &gcv);
    } else {
	CGC = XCreateGC (CDisplay, dummy_window, GCForeground, &gcv);
    }
    return dummy_window;
}

static XFontSet get_font_set (char *name)
{
    XFontSet fontset;
    char **a = 0;
    int b;
    if (!XSupportsLocale ())
#ifdef LC_CTYPE
	fprintf (stderr, "X does not support the locale: %s\n", setlocale (LC_CTYPE, 0));
#else
	fprintf (stderr, "X does not support locale's\n");
#endif
    fontset = XCreateFontSet (CDisplay, name, &a, &b, 0);
    if (!fontset)
	return 0;
    XFreeStringList (a);
    return fontset;
}

/* loads a font and sets the current font to it */
static struct font_object *load_font (char *name, char *xname)
{
    int aa = 0;
    struct font_object *n;
    Window w;
    char *p;

    xname = (char *) strdup (xname);

    if ((p = strchr (xname, '/'))) {
	aa = 1;
	if (atoi (p + 1) != 3) {
	    fprintf (stderr, _("%s: cannot load font\n\t%s\n<font-name>/3 is allowed only.\n"),
		     CAppName, xname);
	    free (xname);
	    return 0;
	}
	*p = '\0';
    }

    n = CMalloc (sizeof (struct font_object));
    memset (n, 0, sizeof (struct font_object));

    if (!option_no_font_set) {
	n->font_set = get_font_set (xname);
	if (!n->font_set)
	    fprintf (stderr,
		     _("%s: display %s cannot load font\n\t%s\nas a font set - trying raw load.\n"),
		     CAppName, DisplayString (CDisplay), xname);
/* font set may have failed only because of an invalid locale, but
   we try load the font anyway */
    }

    if (!n->font_set && !strchr (xname, ',')) {
	n->font_struct = XLoadQueryFont (CDisplay, xname);
	n->free_font_struct = 1;
    }

    if (!n->font_struct && !n->font_set) {
	fprintf (stderr, _("%s: display %s cannot load font\n\t%s\n"), CAppName,
		 DisplayString (CDisplay), xname);
	free (n);
	free (xname);
	return 0;
    }

    n->next = all_fonts;
    current_font = all_fonts = n;
    n->name = (char *) strdup (name);

/* font set may have worked, but if there is only one font, we
   might as well try use a font struct (which draws faster) */
    if (current_font->font_set && !current_font->font_struct) {
	int i, num_fonts, single_font = 1;
	XFontStruct **font_struct_list_return = 0;
	char **font_name_list_return = 0;
	num_fonts =
	    XFontsOfFontSet (current_font->font_set, &font_struct_list_return,
			     &font_name_list_return);
/* check if there is really only one font */
	for (i = 1; i < num_fonts; i++) {
	    if (strcmp (font_name_list_return[0], font_name_list_return[1])) {
		single_font = 0;
		break;
	    }
	}
	if (single_font) {
	    current_font->font_struct = XQueryFont (CDisplay, font_struct_list_return[0]->fid);
	    current_font->free_font_struct = 0;
	}
    }
    if (current_font->font_struct)
	FONT_ANTIALIASING = aa;
    w = get_dummy_gc ();
    if (FONT_USE_FONT_SET)
	XmbDrawImageString (CDisplay, w, current_font->font_set, CGC, 0, 0, " AZ~", 4);
    else
	XDrawImageString (CDisplay, w, CGC, 0, 0, " AZ~", 4);
    FIXED_FONT = check_font_fixed ();
    get_font_dimensions ();
/* -----> FIXME: yes, you actually HAVE to draw with the GC for
   the font to stick. can someone tell me what I am doing wrong? */
    if (!current_font->font_set)
/* font list like rxvt. FIXME: make rxvt and this see same font list */
	current_font->font_set = get_font_set ("7x14,6x10,6x13,8x13,9x15");
    free (xname);
    return current_font;
}

int CPushFont (char *name, char *xname)
{
    struct font_stack *p;
    struct font_object *f;
    f = find_font (name);
    if (f) {
	f->ref++;
    } else {
	f = load_font (name, xname);
	if (!f)
	    return 1;
	f->ref = 1;
    }
    p = CMalloc (sizeof (struct font_stack));
    p->f = f;
    p->next = font_stack;
    font_stack = p;
    current_font = font_stack->f;
    return 0;
}

void CPopFont (void)
{
    struct font_stack *p;
    if (!font_stack) {
	fprintf (stderr, "Huh\n?");
	abort ();
    }
    if (!--font_stack->f->ref) {
	if (font_stack->f->gc)
	    XFreeGC (CDisplay, font_stack->f->gc);
	if (font_stack->f->font_set)
	    XFreeFontSet (CDisplay, font_stack->f->font_set);
	if (font_stack->f->font_struct) {
	    XAaFree (font_stack->f->font_struct->fid);
	    if (font_stack->f->free_font_struct)
		XFreeFont (CDisplay, font_stack->f->font_struct);
	    else
		XFreeFontInfo (0, font_stack->f->font_struct, 0);
	}
	if (font_stack->f->per_char)
	    free (font_stack->f->per_char);
	free (font_stack->f->name);
	free (font_stack->f);
    }
    p = font_stack->next;
    if (p) {
	current_font = p->f;
    } else {
	current_font = 0;
    }
    free (font_stack);
    font_stack = p;
}

void CFreeAllFonts (void)
{
    int i = 0;
    while (font_stack) {
	CPopFont ();
	i++;
    }
}

int CIsFixedFont (void)
{
    return FIXED_FONT;
}

