/* propfont.c - editor text drawing for proportional 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.
*/

#define WIDE_CHAR

#include <config.h>
#include "edit.h"
#if defined (HAVE_MAD) && ! defined (MIDNIGHT) && ! defined (GTK)
#include "mad.h"
#endif
#ifdef HAVE_WCHAR_H
#include <wchar.h>
#endif

/* this file definatively relies on int being 32 bits or more */

int option_long_whitespace = 0;
int cache_width = 0;
int cache_height = 0;

/* must be a multiple of 8 */
#define MAX_LINE_LEN 1024


/* background colors: marked is refers to mouse highlighting, highlighted refers to a found string. */
extern unsigned long edit_abnormal_color, edit_marked_abnormal_color;
extern unsigned long edit_highlighted_color, edit_marked_color;
extern unsigned long edit_normal_background_color;

/* foreground colors */
extern unsigned long edit_normal_foreground_color, edit_bold_color;
extern unsigned long edit_italic_color;

/* cursor color */
extern unsigned long edit_cursor_color;

extern int EditExposeRedraw;
extern int EditClear;

int set_style_color (
#ifdef GTK
			 Window win,
#endif
		      cache_type s, unsigned long *fg, unsigned long *bg)
{
    int fgp, bgp, underlined = 0;
    fgp = s.c.fg;
/* NO_COLOR would give fgp == 255 */
    if (fgp < 0xFF)
	*fg = color_palette (fgp);
    else
	*fg = edit_normal_foreground_color;
    bgp = s.c.bg;
    if (bgp == 0xFE)
	underlined = 1;
    if (bgp < 0xFD)
	*bg = color_palette (bgp);
    else
	*bg = edit_normal_background_color;
    if (!(s.c.style | s.c.fg | s.c.fg))	/* check this first as an optimization */
	return underlined;
    if (s.c.style & MOD_ABNORMAL) {
	*bg = edit_abnormal_color;
	if (s.c.style & MOD_MARKED)
	    *bg = edit_marked_abnormal_color;
    } else if (s.c.style & MOD_HIGHLIGHTED) {
	*bg = edit_highlighted_color;
    } else if (s.c.style & MOD_MARKED) {
	*bg = edit_marked_color;
    }
    if (s.c.style & MOD_BOLD)
	*fg = edit_bold_color;
    if (s.c.style & MOD_ITALIC)
	*fg = edit_italic_color;
    if (s.c.style & MOD_INVERSE) {
	unsigned long t;
	t = *fg;
	*fg = *bg;
	*bg = t;
	if (*bg == COLOR_BLACK)
	    *bg = color_palette (1);
    }
    return underlined;
}

#ifdef GTK
#define set_style_color(s,f,b) set_style_color(win,s,f,b)
#endif

int tab_width = 1;

static inline int next_tab_pos (int x)
{
    return x += tab_width - x % tab_width;
}

/* this now properly uses ctypes */
static inline int convert_to_long_printable (wchar_t c, wchar_t * t)
{
    c &= 0x7FFFFFFFUL;
    if (wc_isgraph (c)) {
	t[0] = c;
	t[1] = 0;
	return FONT_PER_CHAR_256 (c);
    }
    if (c == ' ') {
	if (option_long_whitespace) {
	    t[0] = ' ';
	    t[1] = ' ';
	    t[2] = 0;
	    return FONT_PER_CHAR (' ') + FONT_PER_CHAR (' ');
	} else {
	    t[0] = ' ';
	    t[1] = 0;
	    return FONT_PER_CHAR (' ');
	}
    }
    if (option_international_characters && FONT_PER_CHAR (c)) {
	t[0] = c;
	t[1] = 0;
	return FONT_PER_CHAR (c);
    }
    if (c > 0xFFFF) {
	t[0] = ("0123456789ABCDEF")[(c >> 28) & 0xF];
	t[1] = ("0123456789ABCDEF")[(c >> 24) & 0xF];
	t[2] = ("0123456789ABCDEF")[(c >> 20) & 0xF];
	t[3] = ("0123456789ABCDEF")[(c >> 16) & 0xF];
	t[4] = ("0123456789ABCDEF")[(c >> 12) & 0xF];
	t[5] = ("0123456789ABCDEF")[(c >> 8) & 0xF];
	t[6] = ("0123456789ABCDEF")[(c >> 4) & 0xF];
	t[7] = ("0123456789ABCDEF")[c & 0xF];
	t[8] = 'h';
	t[9] = 0;
	return FONT_PER_CHAR (t[0]) + FONT_PER_CHAR (t[1]) + FONT_PER_CHAR (t[2]) + FONT_PER_CHAR (t[3]) +
	    FONT_PER_CHAR (t[4]);
    }
    if (c > 0xFF) {
	t[0] = ("0123456789ABCDEF")[(c >> 12) & 0xF];
	t[1] = ("0123456789ABCDEF")[(c >> 8) & 0xF];
	t[2] = ("0123456789ABCDEF")[(c >> 4) & 0xF];
	t[3] = ("0123456789ABCDEF")[c & 0xF];
	t[4] = 'h';
	t[5] = 0;
	return FONT_PER_CHAR (t[0]) + FONT_PER_CHAR (t[1]) + FONT_PER_CHAR (t[2]) + FONT_PER_CHAR (t[3]) +
	    FONT_PER_CHAR (t[4]);
    }
    if (c > '~') {
	t[0] = ("0123456789ABCDEF")[c >> 4];
	t[1] = ("0123456789ABCDEF")[c & 0xF];
	t[2] = 'h';
	t[3] = 0;
	return FONT_PER_CHAR (t[0]) + FONT_PER_CHAR (t[1]) + FONT_PER_CHAR (t[2]);
    }
    t[0] = '^';
    t[1] = c + '@';
    t[2] = 0;
    return FONT_PER_CHAR (t[0]) + FONT_PER_CHAR (t[1]);
}

/* same as above but just gets the length */
static inline int width_of_long_printable (wchar_t c)
{
    c &= 0x7FFFFFFFUL;
    if (wc_isgraph (c))
	return FONT_PER_CHAR (c);
    if (c == ' ') {
	if (option_long_whitespace)
	    return FONT_PER_CHAR (' ') + FONT_PER_CHAR (' ');
	else
	    return FONT_PER_CHAR (' ');
    }
    if (option_international_characters && FONT_PER_CHAR (c))
	return FONT_PER_CHAR (c);
    if (c > 0xFF)
	return FONT_PER_CHAR ((unsigned char) ("0123456789ABCDEF")[(c >> 12) & 0xF]) +
	    FONT_PER_CHAR ((unsigned char) ("0123456789ABCDEF")[(c >> 8) & 0xF]) +
	    FONT_PER_CHAR ((unsigned char) ("0123456789ABCDEF")[(c >> 4) & 0xF]) +
	    FONT_PER_CHAR ((unsigned char) ("0123456789ABCDEF")[c & 0xF]) + FONT_PER_CHAR ((unsigned char) 'h');
    if (c > 0xFFFF)
	return FONT_PER_CHAR ((unsigned char) ("0123456789ABCDEF")[(c >> 28) & 0xF]) +
	    FONT_PER_CHAR ((unsigned char) ("0123456789ABCDEF")[(c >> 24) & 0xF]) +
	    FONT_PER_CHAR ((unsigned char) ("0123456789ABCDEF")[(c >> 20) & 0xF]) +
	    FONT_PER_CHAR ((unsigned char) ("0123456789ABCDEF")[(c >> 16) & 0xF]) +
	    FONT_PER_CHAR ((unsigned char) ("0123456789ABCDEF")[(c >> 12) & 0xF]) +
	    FONT_PER_CHAR ((unsigned char) ("0123456789ABCDEF")[(c >> 8) & 0xF]) +
	    FONT_PER_CHAR ((unsigned char) ("0123456789ABCDEF")[(c >> 4) & 0xF]) +
	    FONT_PER_CHAR ((unsigned char) ("0123456789ABCDEF")[c & 0xF]) + FONT_PER_CHAR ((unsigned char) 'h');
    if (c > '~')
	return FONT_PER_CHAR ((unsigned char) ("0123456789ABCDEF")[c >> 4]) +
	    FONT_PER_CHAR ((unsigned char) ("0123456789ABCDEF")[c & 0xF]) + FONT_PER_CHAR ((unsigned char) 'h');
    return FONT_PER_CHAR ('^') + FONT_PER_CHAR (c + '@');
}

int edit_width_of_long_printable (wchar_t c)
{
    return width_of_long_printable (c);
}

/* returns x pixel pos of char at offset *q with x not more than l */
static int calc_text_pos (WEdit * edit, long b, long *q, int l)
{
    int x = 0, xn = 0;
    wchar_t c;
    for (;;) {
	c = edit_get_wide_byte (edit, b);
	switch (c) {
	case -1:
/* no character since used up by a multi-byte sequence */
	    break;
	case '\n':
	    *q = b;
	    if (x > edit->max_column)
		edit->max_column = x;
	    return x;
	case '\t':
	    xn = next_tab_pos (x);
	    break;
	default:
	    xn = x + width_of_long_printable (c);
	    break;
	}
	if (xn > l)
	    break;
	x = xn;
	b++;
    }
    *q = b;
    if (x > edit->max_column)
	edit->max_column = x;
    return x;
}


/* calcs pixel length of the line beginning at b up to upto */
static int calc_text_len (WEdit * edit, long b, long upto)
{
    int x = 0;
    wchar_t c;
    for (;;) {
	if (b == upto) {
	    if (x > edit->max_column)
		edit->max_column = x;
	    return x;
	}
	c = edit_get_wide_byte (edit, b);
	switch (c) {
	case -1:
/* no character since used up by a multi-byte sequence */
	    break;
	case '\n':{
		if (x > edit->max_column)
		    edit->max_column = x;
		return x;
	    }
	case '\t':
	    x = next_tab_pos (x);
	    break;
	default:
	    x += width_of_long_printable (c);
	    break;
	}
	b++;
    }
}

/* If pixels is zero this returns the count of pixels from current to upto. */
/* If upto is zero returns index of pixels across from current. */
long edit_move_forward3 (WEdit * edit, long current, int pixels, long upto)
{
    CPushFont ("editor", 0);
    if (upto) {
	current = calc_text_len (edit, current, upto);
    } else if (pixels) {
	long q;
	calc_text_pos (edit, current, &q, pixels);
	current = q;
    }
    CPopFont ();
    return current;
}

extern int column_highlighting;

/* gets the characters style (eg marked, highlighted) from its position in the edit buffer */
static inline cache_type get_style_fast (WEdit * edit, long q, wchar_t c)
{
    cache_type s;
    unsigned int fg, bg;
    s.c.ch = s._style = 0;
    if (!(wc_isprint (c) || (option_international_characters && FONT_PER_CHAR(c))))
	if (c != '\n' && c != '\t')
	    s.c.style = MOD_ABNORMAL;
    edit_get_syntax_color (edit, q, (int *) &fg, (int *) &bg);
    s.c.fg = fg;
    s.c.bg = bg;
    return s;
}

/* gets the characters style (eg marked, highlighted) from its position in the edit buffer */
static inline cache_type get_style (WEdit * edit, long q, wchar_t c, long m1, long m2, int x)
{
    cache_type s;
    unsigned int fg, bg;
    s.c.ch = s._style = 0;
    if (q == edit->curs1)
	s.c.style |= MOD_CURSOR;
    if (q >= m1 && q < m2) {
	if (column_highlighting) {
	    if ((x >= edit->column1 && x < edit->column2)
		|| (x >= edit->column2 && x < edit->column1))
		s.c.style |= MOD_INVERSE;
	} else {
	    s.c.style |= MOD_MARKED;
	}
    }
    if (q == edit->bracket)
	s.c.style |= MOD_BOLD;
    if (q >= edit->found_start && q < edit->found_start + edit->found_len)
	s.c.style |= MOD_HIGHLIGHTED;
    if (!(wc_isprint (c) || (option_international_characters && FONT_PER_CHAR(c))))
	if (c != '\n' && c != '\t')
	    s.c.style |= MOD_ABNORMAL;
    edit_get_syntax_color (edit, q, (int *) &fg, (int *) &bg);
    s.c.fg = fg;
    s.c.bg = bg;
    return s;
}

static void convert_text (WEdit * edit, long q, cache_type * p, int x, int x_max, int row)
{
    wchar_t c;
    cache_type s;
    long m1, m2, last;
    wchar_t *r, text[12];
    int book_mark_colors[10], book_mark;
    eval_marks (edit, &m1, &m2);
    book_mark = book_mark_query_all (edit, edit->start_line + row, book_mark_colors);
    last = q + (x_max - x) / 2 + 2;	/* for optimization, we say that the last character 
					   of this line cannot have an offset greater than this.
					   This can be used to rule out uncommon text styles,
					   like a character with a cursor, or selected text */

    if (book_mark) {
	int the_end = 0, book_mark_cycle = 0;
	for (;;) {
	    c = edit_get_wide_byte (edit, q);
	    if (!the_end)
		*p = get_style (edit, q, c, m1, m2, x);
	    if (the_end)
		p->c.ch = p->_style = 0;
	    book_mark_cycle = (book_mark_cycle + 1) % book_mark;
	    p->c.fg = book_mark_colors[book_mark_cycle] << 8;
	    p->c.bg = book_mark_colors[book_mark_cycle];
	    switch (c) {
	    case -1:
/* no character since used up by a multi-byte sequence */
		break;
	    case '\n':
		the_end = 1;
		c = ' ';
		q--;
		goto the_default;
	    case '\t':
		if (FIXED_FONT) {
		    int t;
		    t = next_tab_pos (x);
		    t = min (t, x_max);
		    s = *p;
		    s.c.ch = ' ';
		    while (x < t) {
			x += FONT_PER_CHAR(' ');
			*p++ = s;
		    }
		} else {
		    (p++)->c.ch = '\t';
		    x = next_tab_pos (x);
		}
		break;
	    default:
	      the_default:
		x += convert_to_long_printable (c, text);
		r = text;
		s = *p;
		s.c.ch = *r++;
		*p++ = s;
		if (!*r)
		    break;
		s.c.ch = *r++;
		*p++ = s;
		if (!*r)
		    break;
		s.c.ch = *r++;
		*p++ = s;
		if (!*r)
		    break;
		s.c.ch = *r++;
		*p++ = s;
		if (!*r)
		    break;
		s.c.ch = *r++;
		*p++ = s;
		if (!*r)
		    break;
		s.c.ch = *r++;
		*p++ = s;
		if (!*r)
		    break;
		s.c.ch = *r++;
		*p++ = s;
		if (!*r)
		    break;
		s.c.ch = *r++;
		*p++ = s;
		if (!*r)
		    break;
		s.c.ch = *r++;
		*p++ = s;
		break;
	    }
	    if (x >= x_max)
		break;
	    q++;
	}
    } else if ((m2 < q || m1 > last) && (edit->curs1 < q || edit->curs1 > last) && \
	       (edit->found_start + edit->found_len < q || edit->found_start > last) &&
	       (edit->bracket < q || edit->bracket > last)) {
	for (;;) {
	    c = edit_get_wide_byte (edit, q);
	    *p = get_style_fast (edit, q, c);
	    switch (c) {
	    case -1:
/* no character since used up by a multi-byte sequence */
		break;
	    case '\n':
		(p++)->c.ch = ' ';
		p->c.ch = p->_style = 0;
		if (x > edit->max_column)
		    edit->max_column = x;
		return;
	    case '\t':
		if (FIXED_FONT) {
		    int t;
		    t = next_tab_pos (x);
		    t = min (t, x_max);
		    s = *p;
		    s.c.ch = ' ';
		    while (x < t) {
			x += FONT_PER_CHAR(' ');
			*p++ = s;
		    }
		} else {
		    (p++)->c.ch = '\t';
		    x = next_tab_pos (x);
		}
		break;
	    default:
		x += convert_to_long_printable (c, text);
		r = text;
		s = *p;
		s.c.ch = *r++;
		*p++ = s;
		if (!*r)
		    break;
		s.c.ch = *r++;
		*p++ = s;
		if (!*r)
		    break;
		s.c.ch = *r++;
		*p++ = s;
		if (!*r)
		    break;
		s.c.ch = *r++;
		*p++ = s;
		if (!*r)
		    break;
		s.c.ch = *r++;
		*p++ = s;
		if (!*r)
		    break;
		s.c.ch = *r++;
		*p++ = s;
		if (!*r)
		    break;
		s.c.ch = *r++;
		*p++ = s;
		if (!*r)
		    break;
		s.c.ch = *r++;
		*p++ = s;
		if (!*r)
		    break;
		s.c.ch = *r++;
		*p++ = s;
		break;
	    }
	    if (x >= x_max)
		break;
	    q++;
	}
    } else {
	for (;;) {
	    c = edit_get_wide_byte (edit, q);

#if 0
	    if (wc_isgraph(c))
	        printf ("(%d)%c", q, c);
	    else
	        printf ("(%d)%Xh", q, c);
#endif

	    *p = get_style (edit, q, c, m1, m2, x);
	    switch (c) {
	    case -1:
/* no character since used up by a multi-byte sequence */
		break;
	    case '\n':
		(p++)->c.ch = ' ';
		p->c.ch = p->_style = 0;
		if (x > edit->max_column)
		    edit->max_column = x;
		return;
	    case '\t':
		if (FIXED_FONT) {
		    int t;
		    t = next_tab_pos (x);
		    t = min (t, x_max);
		    s = *p;
		    s.c.ch = ' ';
		    while (x < t) {
			x += FONT_PER_CHAR(' ');
			*p++ = s;
		    }
		} else {
		    (p++)->c.ch = '\t';
		    x = next_tab_pos (x);
		}
		break;
	    default:
		x += convert_to_long_printable (c, text);
		r = text;
		s = *p;
		s.c.ch = *r++;
		*p++ = s;
		if (!*r)
		    break;
		s.c.ch = *r++;
		*p++ = s;
		if (!*r)
		    break;
		s.c.ch = *r++;
		*p++ = s;
		if (!*r)
		    break;
		s.c.ch = *r++;
		*p++ = s;
		if (!*r)
		    break;
		s.c.ch = *r++;
		*p++ = s;
		if (!*r)
		    break;
		s.c.ch = *r++;
		*p++ = s;
		if (!*r)
		    break;
		s.c.ch = *r++;
		*p++ = s;
		if (!*r)
		    break;
		s.c.ch = *r++;
		*p++ = s;
		if (!*r)
		    break;
		s.c.ch = *r++;
		*p++ = s;
		break;
	    }
	    if (x >= x_max)
		break;
	    q++;
	}
    }
    if (x > edit->max_column)
	edit->max_column = x;
    p->c.ch = p->_style = 0;
}

static void edit_set_cursor (Window win, int x, int y, int bg, int fg, int width, wchar_t t, int style)
{
#ifdef GTK
    gdk_gc_set_foreground (win->gc, &win->color[18]);
    gdk_draw_rectangle (win->text_area, win->gc, 0, x, y + FONT_OVERHEAD, width - 1, FONT_HEIGHT - 1);
#else
    CSetColor (edit_cursor_color);
    if (style & MOD_REVERSE)
	CLine (win, x + width - 1, y + FONT_OVERHEAD, x + width - 1, y + FONT_HEIGHT - 1);	/* non focussed cursor form */
    else
	CLine (win, x, y + FONT_OVERHEAD, x, y + FONT_HEIGHT - 1);	/* non focussed cursor form */
    CLine (win, x, y + FONT_OVERHEAD, x + width - 1, y + FONT_OVERHEAD);
    set_cursor_position (win, x, y, width, FONT_HEIGHT, CURSOR_TYPE_EDITOR, t, bg, fg, style);	/* widget library's flashing cursor */
#endif
}

static inline int next_tab (int x, int scroll_right)
{
    return next_tab_pos (x - scroll_right - EDIT_TEXT_HORIZONTAL_OFFSET) - x + scroll_right + EDIT_TEXT_HORIZONTAL_OFFSET;
}

static int draw_tab (Window win, int x, int y, cache_type s, int scroll_right)
{
    int l;
#ifdef GTK
    GdkColor fg, bg;
#else
    unsigned long fg, bg;
#endif
    l = next_tab (x, scroll_right);
#ifdef GTK
    set_style_color (s, &fg.pixel, &bg.pixel);
    gdk_gc_set_foreground (win->gc, &bg);
    gdk_draw_rectangle (win->text_area, win->gc, 1, x, y + FONT_OVERHEAD, l, FONT_HEIGHT);
/* if we printed a cursor: */
    if (s.c.style & MOD_CURSOR)
	edit_set_cursor (win, x, y, bg.pixel, fg.pixel, FONT_PER_CHAR(' '), ' ', s.c.style);
#else
    set_style_color (s, &fg, &bg);
    CSetColor (bg);
    CRectangle (win, x, y + FONT_OVERHEAD, l, FONT_HEIGHT);
/* if we printed a cursor: */
    if (s.c.style & MOD_CURSOR)
	edit_set_cursor (win, x, y, bg, fg, FONT_PER_CHAR(' '), ' ', s.c.style);
#endif
    return x + l;
}

#ifdef GTK
#include <gdk/gdk.h>
#endif

static inline void draw_space (Window win, int x, int y, cache_type s, int l)
{
#ifdef GTK
    GdkColor fg, bg;
#else
    unsigned long fg, bg;
#endif
#ifdef GTK
    set_style_color (s, &fg.pixel, &bg.pixel);
    gdk_gc_set_foreground (win->gc, &bg);
    gdk_draw_rectangle (win->text_area, win->gc, 1, x, y + FONT_OVERHEAD, l, FONT_HEIGHT);
#else
    set_style_color (s, &fg, &bg);
    CSetColor (bg);
    CRectangle (win, x, y + FONT_OVERHEAD, l, FONT_HEIGHT);
/* if we printed a cursor: */
    if (s.c.style & MOD_CURSOR)
	edit_set_cursor (win, x, y, bg, fg, FONT_PER_CHAR(' '), ' ', s.c.style);
#endif
}

#ifdef GTK

void
gdk_draw_image_text (GdkDrawable *drawable,
	       GdkFont     *font,
	       GdkGC       *gc,
	       gint         x,
	       gint         y,
	       const gchar *text,
	       gint         text_length)
{
  GdkWindowPrivate *drawable_private;
  GdkFontPrivate *font_private;
  GdkGCPrivate *gc_private;

  g_return_if_fail (drawable != NULL);
  g_return_if_fail (font != NULL);
  g_return_if_fail (gc != NULL);
  g_return_if_fail (text != NULL);

  drawable_private = (GdkWindowPrivate*) drawable;
  if (drawable_private->destroyed)
    return;
  gc_private = (GdkGCPrivate*) gc;
  font_private = (GdkFontPrivate*) font;

  if (font->type == GDK_FONT_FONT)
    {
      XFontStruct *xfont = (XFontStruct *) font_private->xfont;
      XSetFont(drawable_private->xdisplay, gc_private->xgc, xfont->fid);
      if ((xfont->min_byte1 == 0) && (xfont->max_byte1 == 0))
	{
	  XDrawImageString (drawable_private->xdisplay, drawable_private->xwindow,
		       gc_private->xgc, x, y, text, text_length);
	}
      else
	{
	  XDrawImageString16 (drawable_private->xdisplay, drawable_private->xwindow,
			 gc_private->xgc, x, y, (XChar2b *) text, text_length / 2);
	}
    }
  else if (font->type == GDK_FONT_FONTSET)
    {
      XFontSet fontset = (XFontSet) font_private->xfont;
      XmbDrawImageString (drawable_private->xdisplay, drawable_private->xwindow,
		     fontset, gc_private->xgc, x, y, text, text_length);
    }
  else
    g_error("undefined font type\n");
}


#endif

static int draw_string (Window win, int x, int y, cache_type s, XChar2b *text, wchar_t *textwc, int length)
{
#ifdef GTK
    GdkColor fg, bg;
#else
    unsigned long fg, bg;
    int underlined, l;
#endif
#ifdef GTK
    set_style_color (s, &fg.pixel, &bg.pixel);
    gdk_gc_set_background (win->gc, &bg);
    gdk_gc_set_foreground (win->gc, &fg);
    gdk_draw_image_text (win->text_area, GTK_WIDGET (win)->style->font, win->gc, x + FONT_OFFSET_X, y + FONT_OFFSET_Y, text, length);
#else
    underlined = set_style_color (s, &fg, &bg);
    CSetBackgroundColor (bg);
    CSetColor (fg);
    CImageTextWC (win, x + FONT_OFFSET_X, y + FONT_OFFSET_Y, text, textwc, length);
    l = CImageTextWidthWC (text, textwc, length);
    if (underlined) {
	int i, h, inc;
	inc = FONT_MEAN_WIDTH * 2 / 3;
	CSetColor (color_palette (18));
	h = (x / inc) & 1;
	CLine (win, x, y + FONT_HEIGHT + FONT_OVERHEAD - 1 - h, x + min (l, inc - (x % inc) - 1), y + FONT_HEIGHT + FONT_OVERHEAD - 1 - h);
	h = h ^ 1;
	for (i = inc - min (l, (x % inc)); i < l; i += inc) {
	    CLine (win, x + i, y + FONT_HEIGHT + FONT_OVERHEAD - 1 - h, x + min (l, i + inc - 1), y + FONT_HEIGHT + FONT_OVERHEAD - 1 - h);
	    h = h ^ 1;
	}
    }
#endif
/* if we printed a cursor: */
#ifdef GTK
    if (s.c.style & MOD_CURSOR)
	edit_set_cursor (win, x, y, bg.pixel, fg.pixel, FONT_PER_CHAR(*textwc), *textwc, s.c.style);
    return x + gdk_text_width (GTK_WIDGET (win)->style->font, text, length);
#else
    if (s.c.style & MOD_CURSOR)
	edit_set_cursor (win, x, y, bg, fg, FONT_PER_CHAR(*textwc), *textwc, s.c.style);
    return x + l;
#endif
}

#define STYLE_DIFF (cache->_style != line->_style || cache->c.ch != line->c.ch \
	    || ((cache->c.style | line->c.style) & MOD_CURSOR) \
	    || !(cache->c.ch | cache->_style) || !(line->c.ch | line->_style))

int get_ignore_length (cache_type *cache, cache_type *line)
{
    int i;
    for (i = 0; i < cache_width; i++, line++, cache++) {
	if (STYLE_DIFF)
	    return i;
    }
    return cache_width;
}

static inline size_t lwstrnlen (const cache_type * s, size_t count)
{
    const cache_type *sc;
    for (sc = s; count-- && (sc->c.ch | sc->_style) != 0; ++sc);
    return sc - s;
}

static inline size_t lwstrlen (const cache_type * s)
{
    const cache_type *sc;
    for (sc = s; (sc->c.ch | sc->_style) != 0; ++sc);
    return sc - s;
}

int get_ignore_trailer (cache_type *cache, cache_type *line, int length)
{
    int i;
    int cache_len, line_len;
    cache_len = lwstrnlen (cache, cache_width);
    line_len = lwstrlen (line);

    if (line_len > cache_len)
	for (i = line_len - 1; i >= cache_len && i >= length; i--) {
	    if (line[i].c.ch == ' ' && !(line[i].c.style | line[i].c.fg | line[i].c.bg))
		continue;
	    return i + 1;
	}
    for (i = cache_len - 1, line = line + i, cache = cache + i; i > length; i--, line--, cache--)
	if (STYLE_DIFF)
	    return i + 1;

    return length + 1;
}

/* erases trailing bit of old line if a new line is printed over a longer old line */
static void cover_trail (Window win, int x_start, int x_new, int x_old, int y)
{
    if (x_new < EDIT_TEXT_HORIZONTAL_OFFSET)
	x_new = EDIT_TEXT_HORIZONTAL_OFFSET;
    if (x_new < x_old) {	/* no need to print */
#ifdef GTK
	gdk_gc_set_foreground (win->gc, &win->color[1]);
	gdk_draw_rectangle (win->text_area, win->gc, 1, x_new, y + FONT_OVERHEAD, x_old - x_new, FONT_HEIGHT);
#else
	CSetColor (edit_normal_background_color);
	CRectangle (win, x_new, y + FONT_OVERHEAD, x_old - x_new, FONT_HEIGHT + (FONT_OVERHEAD != 0 && !FIXED_FONT));
#endif
    } else {
#ifdef GTK
	gdk_gc_set_foreground (win->gc, &win->color[1]);
#else
	CSetColor (edit_normal_background_color);
#endif
    }
/* true type fonts print stuff out of the bounding box (aaaaaaaaarrrgh!!) */
    if (!FIXED_FONT)
	if (FONT_OVERHEAD && x_new > EDIT_TEXT_HORIZONTAL_OFFSET)
#ifdef GTK
	    gdk_draw_line (win->text_area, win->gc, max (x_start, EDIT_TEXT_HORIZONTAL_OFFSET), y + FONT_HEIGHT + FONT_OVERHEAD, x_new - 1, y + FONT_HEIGHT + FONT_OVERHEAD);
#else
	    CLine (win, max (x_start, EDIT_TEXT_HORIZONTAL_OFFSET), y + FONT_HEIGHT + FONT_OVERHEAD, x_new - 1, y + FONT_HEIGHT + FONT_OVERHEAD);
#endif
}

#define NOT_VALID (-2000000000)

struct cache_line {
    int x0, x1;
    cache_type *data;
};

struct cache_line *cache_lines = 0;

void edit_free_cache_lines (void)
{
    int i;
    if (cache_lines) {
	for (i = 0; i < cache_height; i++)
	    free (cache_lines[i].data);
	free (cache_lines);
	cache_lines = 0;
    }
}

static void edit_realloc_cache_lines (int width, int height)
{
    if (width > cache_width || height > cache_height) {
	int i;
	edit_free_cache_lines ();
	cache_width = max (width + 10, cache_width);
	cache_height = max (height + 10, cache_height);
	cache_lines = malloc (sizeof (struct cache_line) * cache_height);
	memset (cache_lines, 0, sizeof (struct cache_line) * cache_height);
	for (i = 0; i < cache_height; i++) {
	    cache_lines[i].data = malloc (sizeof (cache_type) * (cache_width + 1));
	    memset (cache_lines[i].data, 0, sizeof (cache_type) * (cache_width + 1));
	    cache_lines[i].x0 = NOT_VALID;
	    cache_lines[i].x1 = 10000;
	}
    }
}

#define IS_HEBREW(c) (((c) >= 0x0590UL && (c) <= 0x05FFUL))

int option_reverse_hebrew = 1;

static void reverse_text (cache_type * line)
{
    int i, n;
    if (option_reverse_hebrew) {
	while (line->c.ch | line->_style) {
	    while (!IS_HEBREW (line->c.ch) && (line->c.ch | line->_style))
		line++;
	    for (n = 0; (IS_HEBREW (line[n].c.ch) || (line[n].c.ch == ' ')) && (line[n].c.ch | line[n]._style); n++);
	    while (n && !IS_HEBREW (line[n - 1].c.ch))
		n--;
	    n--;
	    for (i = 0; i < (n + 1) / 2; i++) {
		cache_type t;
		t = line[i];
		line[i] = line[n - i];
		line[i].c.style |= MOD_REVERSE;
		line[n - i] = t;
		line[n - i].c.style |= MOD_REVERSE;
	    }
	    line += n + 1;
	}
    }
}

void edit_draw_proportional (void *data,
	   void (*converttext) (void *, long, cache_type *, int, int, int),
	   int calctextpos (void *, long, long *, int),
				int scroll_right,
				Window win,
				int x_max,
				long b,
				int row,
				int y,
				int x_offset,
				int tabwidth)
{
    static Window last = 0;
    cache_type style, line[MAX_LINE_LEN], *p;
    XChar2b text[128];
    wchar_t textwc[128];
    int x0, x, ignore_text = 0, ignore_trailer = 2000000000, j, i;
    long q;

    tab_width = tabwidth;
    if (option_long_whitespace)
	tab_width = tabwidth *= 2;

    x_max -= 3;

    edit_realloc_cache_lines (x_max / 3, row + 1);

/* if its not the same window, reset the screen rememberer */
    if (last != win || !win) {
	last = win;
	for (i = 0; i < cache_height; i++) {
	    cache_lines[i].x0 = NOT_VALID;
	    cache_lines[i].x1 = x_max;
	}
	if (!win)
	    return;
    }

/* get point to start drawing */
    x0 = (*calctextpos) (data, b, &q, -scroll_right + x_offset);
/* q contains the offset in the edit buffer */

/* translate this line into printable characters with a style (=color) high byte */
    for (i = 0; i < MAX_LINE_LEN; i += 8) {
	line[i]._style = line[i].c.ch = 0;
	line[i + 1]._style = line[i + 1].c.ch = 0;
	line[i + 2]._style = line[i + 2].c.ch = 0;
	line[i + 3]._style = line[i + 3].c.ch = 0;
	line[i + 4]._style = line[i + 4].c.ch = 0;
	line[i + 5]._style = line[i + 5].c.ch = 0;
	line[i + 6]._style = line[i + 6].c.ch = 0;
	line[i + 7]._style = line[i + 7].c.ch = 0;
    }
    (*converttext) (data, q, line, x0, x_max - scroll_right - EDIT_TEXT_HORIZONTAL_OFFSET, row);
    reverse_text (line);

/* adjust for the horizontal scroll and border */
    x0 += scroll_right + EDIT_TEXT_HORIZONTAL_OFFSET;
    x = x0;

/* is some of the line identical to that already printed so that we can ignore it? */
    if (!EditExposeRedraw) {
	if (cache_lines[row].x0 == x0 && row < cache_height) {	/* i.e. also  && cache_lines[row].x0 != NOT_VALID */
	    ignore_text = get_ignore_length (cache_lines[row].data, line);
	    if (FIXED_FONT)
		ignore_trailer = get_ignore_trailer (cache_lines[row].data, line, ignore_text);
	}
    }
    p = line;
    j = 0;
    while (p->c.ch | p->_style) {
	if (p->c.style & MOD_PIXMAP) {
#ifdef STILL_TO_BE_SUPPORTED
	    x += edit_insert_pixmap (win, x, y, p->c.ch);
		/* the pixmap will be clipped, if it's taller than the
		   current font, else centred top to bottom */
#endif
	    j++;
	    p++;
	    continue;
	}
	if (p->c.style & MOD_TAB) {
	    draw_space (win, x, y, *p, p->c.ch);
	    x += p->c.ch;
	    j++;
	    p++;
	} else if (p->c.ch == '\t') {
	    j++;
	    if (j > ignore_text && j < ignore_trailer + 1)
		x = draw_tab (win, x, y, *p, scroll_right);
	    else
		x += next_tab (x, scroll_right);
	    p++;
	} else {
	    style = *p;
	    style.c.ch = 0;
	    i = 0;
	    do {
		text[i].byte2 = (unsigned char) p->c.ch;
		text[i].byte1 = (unsigned char) (p->c.ch >> 8);
		textwc[i++] = (p++)->c.ch;
		j++;
		if (j == ignore_text || j == ignore_trailer)
		    break;
	    } while (i < 128 && p->c.ch && style._style == p->_style && p->c.ch != '\t');

	    if (j > ignore_text && j < ignore_trailer + 1) {
		x = draw_string (win, x, y, style, text, textwc, i);
	    } else {
#ifdef GTK
		x += gdk_text_width (GTK_WIDGET(win)->style->font, text, i);
#else
		x += CImageTextWidthWC (text, textwc, i);
#endif
	    }
	}
    }

    x = min (x, x_max);
    if (!EditExposeRedraw || EditClear)
	cover_trail (win, x0, x, cache_lines[row].x1, y);
    memcpy (&(cache_lines[row].data[ignore_text]),
	    &(line[ignore_text]),
	 (min (j, cache_width) - ignore_text) * sizeof (cache_type));

    cache_lines[row].data[min (j, cache_width)].c.ch = 0;
    cache_lines[row].data[min (j, cache_width)]._style = 0;

    cache_lines[row].x0 = x0;
    cache_lines[row].x1 = x;
    if (EditExposeRedraw)
	last = 0;
    else
	last = win;
}


void edit_draw_this_line_proportional (WEdit * edit, long b, int row, int start_column, int end_column)
{
    int fg, bg;
    if (row < 0 || row >= edit->num_widget_lines)
	return;

    if (row + edit->start_line > edit->total_lines)
	b = edit->last_byte + 1;		/* force b out of range of the edit buffer for blanks lines */

    if (end_column > CWidthOf (edit->widget))
	end_column = CWidthOf (edit->widget);

    edit_get_syntax_color (edit, b - 1, &fg, &bg);

    edit_draw_proportional (edit,
			    (void (*) (void *, long, cache_type *, int, int, int)) convert_text,
			    (int (*) (void *, long, long *, int)) calc_text_pos,
			    edit->start_col, CWindowOf (edit->widget),
			    end_column, b, row, row * FONT_PIX_PER_LINE + EDIT_TEXT_VERTICAL_OFFSET,
			    EditExposeRedraw ? start_column : 0, FONT_PER_CHAR(' ') * TAB_SIZE);
}


/*********************************************************************************/
/*         The remainder is for the text box widget                              */
/*********************************************************************************/

static int calc_text_pos_str (unsigned char *text, long b, long *q, int l)
{
    int x = 0, c = 0, xn = 0, d;
    for (;;) {
	d = c;
	c = (unsigned char) text[b];
	switch (c) {
	case '\0':
	case '\n':
	    *q = b;
	    return x;
	case '\t':
	    xn = next_tab_pos (x);
	    break;
	case '\r':
	    break;
	case '\b':
	    if (d)
		xn = x - FONT_PER_CHAR(d);
	    break;
	default:
	    if (!FONT_PER_CHAR (c))
		c = ' ';
	    xn = x + FONT_PER_CHAR(c);
	    break;
	}
	if (xn > l)
	    break;
	x = xn;
	b++;
    }
    *q = b;
    return x;
}

int prop_font_strcolmove (unsigned char *str, int i, int column)
{
    long q;
    CPushFont ("editor", 0);
    calc_text_pos_str (str, i, &q, column * FONT_MEAN_WIDTH);
    CPopFont ();
    return q;
}

#ifndef GTK

/* b is the beginning of the line. l is the length in pixels up to a point 
   on some character which is unknown. The character pos is returned in
   *q and the characters pixel x pos from b is return'ed. */
int calc_text_pos2 (CWidget * w, long b, long *q, int l)
{
    int r;
    CPushFont ("editor", 0);
    r = calc_text_pos_str ((unsigned char *) w->text, b, q, l);
    CPopFont ();
    return r;
}

int highlight_this_line;

/* this is for the text widget (i.e. nroff formatting) */
void convert_text2 (CWidget * w, long q, cache_type * line, int x, int x_max, int row)
{
    int c = 0, d;
    cache_type s, *p;
    long m1, m2;

    m1 = min (w->mark1, w->mark2);
    m2 = max (w->mark1, w->mark2);

    p = line;
    p->c.ch = p->_style = 0;
    for (;;) {
	d = c;
	c = (unsigned char) w->text[q];
	p[1].c.ch = p[1]._style = 0;
	p->c.fg = p->c.bg = 0xFF;	/* default background colors */
	if (highlight_this_line)
	    p->c.style |= MOD_HIGHLIGHTED;
	if (q >= m1 && q < m2)
	    p->c.style |= MOD_MARKED;
	switch (c) {
	case '\0':
	case '\n':
	    (p++)->c.ch |= ' ';
	    if (highlight_this_line) {
		q--;
		x += FONT_PER_CHAR (' ');
	    } else
		return;
	    break;
	case '\t':
	    if (FIXED_FONT) {
		int i;
		i = next_tab_pos (x) - x;
		x += i;
		s = *p;
		while (i > 0) {
		    i -= FONT_PER_CHAR (' ');
		    (p++)->c.ch = s.c.ch | ' ';
		    p->c.ch = p->_style = 0;
		}
	    } else {
		(p++)->c.ch |= '\t';
		x = next_tab_pos (x);
	    }
	    break;
	case '\r':
	    break;
	case '\b':
	    if (d) {
		--p;
		x -= FONT_PER_CHAR (d);
		if (d == '_')
		    p->c.style |= MOD_ITALIC;
		else
		    p->c.style |= MOD_BOLD;
	    }
	    break;
	default:
	    if (!FONT_PER_CHAR (c)) {
		c = ' ';
		p->c.style |= MOD_ABNORMAL;
	    }
	    x += FONT_PER_CHAR (c);
	    (p++)->c.ch = c;
	    break;
	}
	if (x > x_max)
	    break;
	q++;
    }
    p->c.ch = p->_style = 0;
}


#endif


