/* glyphs.c -- Construction of character images
   Copyright (C) 1994 John Harper <jsh@ukc.ac.uk>

   This file is part of Jade.

   Jade 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, or (at your option)
   any later version.

   Jade 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 Jade; see the file COPYING.	If not, write to
   the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.  */

#include "jade.h"
#include "jade_protos.h"

#include <string.h>
#include <stdlib.h>

#ifdef NEED_MEMORY_H
# include <memory.h>
#endif

_PR long make_glyph_array(TX *, const u_char *, long, long, u_char *, long, long);
_PR long string_glyph_length(TX *, const u_char *, long);
_PR long glyph_col(TX *, long, long);
_PR long char_col(TX *, long, long);
_PR void calc_cursor_offset(VW *);
_PR void adjust_cursor_to_glyph(VW *);
_PR u_char *char_glyphs(TX *, u_char);
_PR long expand_tabs(TX *, u_char *, long, long, long, long *);

_PR void glyphtable_sweep(void);
_PR void glyphtable_prin(VALUE, VALUE);
_PR void glyphs_init(void);
_PR void glyphs_kill(void);

/* This table stores the number of glyphs which have to be printed to
   make up each character. 0 is a special case -- TABSIZE spaces.  */
typedef char glyph_widths_t[256];

/* This stores the glyphs which have to be printed to create each
   single character, each character (except tabs) can be made of
   no more than 4 glyphs. The above table is used to decide how
   many to use out of the possible 4.  */
typedef u_char glyph_glyphs_t[256][4];

typedef struct _GlyphTable{
    u_char		gt_Type;
    u_char		gt_Flags;
    struct _GlyphTable *gt_Next;
    glyph_widths_t	gt_Widths;
    glyph_glyphs_t	gt_Glyphs;
} GlyphTable;
#define GTF_STATIC 1		/* Don't free() this table */

static GlyphTable default_glyph_table = {
    V_GlyphTable,
    GTF_STATIC,
    NULL,
    {
	2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 2, 2, 2, 2, 2, 2,
	2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
	1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
	1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
	1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
	1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
	1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
	1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2,
	4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
	4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
	4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
	4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
	4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
	4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
	4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
	4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4
    },
    {
	"^@  ", "^A  ", "^B  ", "^C  ", "^D  ", "^E  ", "^F  ", "^G  ",
	"^H  ", " TAB", "^J  ", "^K  ", "^L  ", "^M  ", "^N  ", "^O  ",
	"^P  ", "^Q  ", "^R  ", "^S  ", "^T  ", "^U  ", "^V  ", "^W  ",
	"^X  ", "^Y  ", "^Z  ", "^[  ", "^\\  ", "^]  ", "^^  ", "^_  ",
	"    ", "!   ", "\"   ", "#   ", "$   ", "%   ", "&   ", "'   ",
	"(   ", ")   ", "*   ", "+   ", ",   ", "-   ", ".   ", "/   ",
	"0   ", "1   ", "2   ", "3   ", "4   ", "5   ", "6   ", "7   ",
	"8   ", "9   ", ":   ", ";   ", "<   ", "=   ", ">   ", "?   ",
	"@   ", "A   ", "B   ", "C   ", "D   ", "E   ", "F   ", "G   ",
	"H   ", "I   ", "J   ", "K   ", "L   ", "M   ", "N   ", "O   ",
	"P   ", "Q   ", "R   ", "S   ", "T   ", "U   ", "V   ", "W   ",
	"X   ", "Y   ", "Z   ", "[   ", "\\   ", "]   ", "^   ", "_   ",
	"`   ", "a   ", "b   ", "c   ", "d   ", "e   ", "f   ", "g   ",
	"h   ", "i   ", "j   ", "k   ", "l   ", "m   ", "n   ", "o   ",
	"p   ", "q   ", "r   ", "s   ", "t   ", "u   ", "v   ", "w   ",
	"x   ", "y   ", "z   ", "{   ", "|   ", "}   ", "~   ", "^?  ",
	"\\200", "\\201", "\\202", "\\203", "\\204", "\\205", "\\206", "\\207",
	"\\210", "\\211", "\\212", "\\213", "\\214", "\\215", "\\216", "\\217",
	"\\220", "\\221", "\\222", "\\223", "\\224", "\\225", "\\226", "\\227",
	"\\230", "\\231", "\\232", "\\233", "\\234", "\\235", "\\236", "\\237",
	"\\240", "\\241", "\\242", "\\243", "\\244", "\\245", "\\246", "\\247",
	"\\250", "\\251", "\\252", "\\253", "\\254", "\\255", "\\256", "\\257",
	"\\260", "\\261", "\\262", "\\263", "\\264", "\\265", "\\266", "\\267",
	"\\270", "\\271", "\\272", "\\273", "\\274", "\\275", "\\276", "\\277",
	"\\300", "\\301", "\\302", "\\303", "\\304", "\\305", "\\306", "\\307",
	"\\310", "\\311", "\\312", "\\313", "\\314", "\\315", "\\316", "\\317",
	"\\320", "\\321", "\\322", "\\323", "\\324", "\\325", "\\326", "\\327",
	"\\330", "\\331", "\\332", "\\333", "\\334", "\\335", "\\336", "\\337",
	"\\340", "\\341", "\\342", "\\343", "\\344", "\\345", "\\346", "\\347",
	"\\350", "\\351", "\\252", "\\353", "\\354", "\\355", "\\356", "\\357",
	"\\360", "\\361", "\\362", "\\363", "\\364", "\\365", "\\366", "\\367",
	"\\370", "\\371", "\\372", "\\373", "\\374", "\\375", "\\376", "\\377"
    }
};

static GlyphTable *gt_chain = &default_glyph_table;

/* From the array of characters SRC, build an array of glyph codes DST.
   SRC contains at least SRC-LEN characters. On return, there is guaranteed
   to be no more than DST-LEN glyph codes in DST, the actual number is
   the return value of the function. No glyphs are copied to DST until
   DST-START glyphs have gone before. SRC-ORIG is the *true glyph* position
   of SRC, for calculating tab sizes.  */
long
make_glyph_array(TX *tx, const u_char *src, long srcOrig, long srcLen,
		 register u_char *dst, long dstStart, long dstLen)
{
    glyph_widths_t *width_table = &VGLYPHTAB(tx->tx_GlyphTable)->gt_Widths;
    glyph_glyphs_t *glyph_table = &VGLYPHTAB(tx->tx_GlyphTable)->gt_Glyphs;
    register long i = 0;
    if(dstStart != 0)
    {
	/* Skip DSTSTART glyphs */
	register long j = 0;
	while((j < dstStart) && (srcLen-- > 0))
	{
	    register int w = (*width_table)[*src++];
	    if(w == 0)
	    {
		j += tx->tx_TabSize - ((j + srcOrig) % tx->tx_TabSize);
		if(j > dstStart)
		{
		    i = j - dstStart;
		    memset(dst, ' ', i);
		    dst += i;
		    break;
		}
	    }
	    else
	    {
		j += w;
		if(j > dstStart)
		{
		    i = j - dstStart;
		    memcpy(dst, &(*glyph_table)[src[-1]][w-i], i);
		    dst += i;
		    break;
		}
	    }
	}
    }
    while((i < dstLen) && (srcLen-- > 0))
    {
	register u_char c;
	switch((*width_table)[c = *src++])
	{
	case 0:
	    {
		/* TAB special case */
		register int size = tx->tx_TabSize - ((i + srcOrig + dstStart)
						      % tx->tx_TabSize);
		memset(dst, ' ', size);
		dst += size;
		i += size;
	    }
	    break;
	case 1:
	    *dst++ = (*glyph_table)[c][0];
	    i++;
	    break;
	case 2:
	    *dst++ = (*glyph_table)[c][0];
	    *dst++ = (*glyph_table)[c][1];
	    i += 2;
	    break;
	case 3:
	    *dst++ = (*glyph_table)[c][0];
	    *dst++ = (*glyph_table)[c][1];
	    *dst++ = (*glyph_table)[c][2];
	    i += 3;
	    break;
	case 4:
	    *dst++ = (*glyph_table)[c][0];
	    *dst++ = (*glyph_table)[c][1];
	    *dst++ = (*glyph_table)[c][2];
	    *dst++ = (*glyph_table)[c][3];
	    i += 4;
	    break;
	}
    }
    return(i);
}

/* Returns the number of glyphs needed to draw the string SRC.	*/
long
string_glyph_length(TX *tx, const u_char *src, long srcLen)
{
    glyph_widths_t *width_table = &VGLYPHTAB(tx->tx_GlyphTable)->gt_Widths;
    register long w;
    for(w = 0; srcLen-- > 0;)
    {
	register int w1 = (*width_table)[*src++];
	if(w1 == 0)
	    w += tx->tx_TabSize - (w % tx->tx_TabSize);
	else
	    w += w1;
    }
    return(w);
}

/* Return the glyph index of (COL,LINE) in TX.	*/
long
glyph_col(TX *tx, long col, long linenum)
{
    LINE *line = tx->tx_Lines + linenum;
    if(col >= line->ln_Strlen)
    {
	return((string_glyph_length(tx, line->ln_Line, line->ln_Strlen - 1)
		+ (col - (line->ln_Strlen - 1))));
    }
    else
	return(string_glyph_length(tx, line->ln_Line, col));
}

/* Find how many chars to glyph position col. */
long
char_col(TX *tx, long col, long linenum)
{
    LINE *line = tx->tx_Lines + linenum;
    u_char *src = line->ln_Line;
    long srclen = line->ln_Strlen - 1;
    glyph_widths_t *width_table = &VGLYPHTAB(tx->tx_GlyphTable)->gt_Widths;
    register long w = 0;
    while((w < col) && (srclen-- != 0))
    {
	register int w1 = (*width_table)[*src++];
	if(w1 == 0)
	    w += tx->tx_TabSize - (w % tx->tx_TabSize);
	else
	    w += w1;
    }
    if(srclen == -1)
	return((line->ln_Strlen - 1) + (col - w));
    else
	return(src - line->ln_Line);
}

void
calc_cursor_offset(VW *vw)
{
    TX *tx = vw->vw_Tx;
    if(!((tx == vw->vw_LastCursorTx)
	 && (tx->tx_Changes == vw->vw_LastCursorChanges)
	 && POS_EQUAL_P(&vw->vw_CursorPos, &vw->vw_LastCursorPos)))
    {
	/* Have to recalculate the number of glyphs before the cursor. */
	vw->vw_LastCursorOffset = glyph_col(tx, vw->vw_CursorPos.pos_Col,
					    vw->vw_CursorPos.pos_Line);
	vw->vw_LastCursorTx = tx;
	vw->vw_LastCursorChanges = tx->tx_Changes;
	vw->vw_LastCursorPos = vw->vw_CursorPos;
    }
}

/* Sets the vw_CursorPos.pos_Col so that the cursor's glyph pos is the same
   as vw_LastCursorOffset.  */
void
adjust_cursor_to_glyph(VW *vw)
{
    vw->vw_CursorPos.pos_Col = char_col(vw->vw_Tx, vw->vw_LastCursorOffset,
					vw->vw_CursorPos.pos_Line);
}

u_char *
char_glyphs(TX *tx, u_char ch)
{
    return(&VGLYPHTAB(tx->tx_GlyphTable)->gt_Glyphs[ch][0]);
}

/* Expands any tabs in the line LINE-NUM in TX, between the glyph
   positions START-GLYPH and END-GLYPH into the buffer DST. The
   actual number of characters copied into DST (won't be any more
   than (END-GLYPH - START-GLYPH) is returned.	If WIDTH-P is non-NULL
   the true width (in glyphs) of the copied portion is stored there. */
long
expand_tabs(TX *tx, u_char *dst, long start_glyph, long end_glyph,
	    long linenum, long *width_p)
{
    LINE *line = tx->tx_Lines + linenum;
    u_char *src = line->ln_Line;
    u_char *orig_dst = dst;
    long srclen = line->ln_Strlen - 1;
    glyph_widths_t *width_table = &VGLYPHTAB(tx->tx_GlyphTable)->gt_Widths;
    register long i = 0;
    if(start_glyph > 0)
    {
	while(srclen-- != 0)
	{
	    register int w1 = (*width_table)[*src++];
	    if(w1 == 0)
	    {
		i += tx->tx_TabSize - (i % tx->tx_TabSize);
		if(i > start_glyph)
		{
		    memset(dst, ' ', i - start_glyph);
		    dst += i - start_glyph;
		    break;
		}
	    }
	    else
	    {
		i += w1;
		if(i > start_glyph)
		{
		    *dst++ = src[-1];
		    break;
		}
	    }
	}
    }
    if(srclen == -1)
	return(dst - orig_dst);
    while(srclen-- != 0)
    {
	u_char c = *src++;
	register int w1 = (*width_table)[c];
	if(w1 == 0)
	{
	    w1 = tx->tx_TabSize - (i % tx->tx_TabSize);
	    if(w1 + i >= end_glyph)
	    {

		memset(dst, ' ', end_glyph - i);
		dst += end_glyph - i;
		break;
	    }
	    memset(dst, ' ', w1);
	    dst += w1;
	    i += w1;
	}
	else
	{
	    *dst++ = c;
	    i += w1;
	    if(i >= end_glyph)
		break;
	}
    }
    if(width_p)
	*width_p = i - start_glyph;
    return(dst - orig_dst);
}

_PR VALUE cmd_glyph_table_p(VALUE arg);
DEFUN("glyph-table-p", cmd_glyph_table_p, subr_glyph_table_p, (VALUE arg), V_Subr1, DOC_glyph_table_p) /*
::doc:glyph_table_p::
glyph-table-p ARG

Returns t if ARG is a glyph-table.
::end:: */
{
    return(GLYPHTABP(arg) ? sym_t : sym_nil);
}

_PR VALUE cmd_char_to_glyph_pos(VALUE vpos, VALUE tx);
DEFUN("char-to-glyph-pos", cmd_char_to_glyph_pos, subr_char_to_glyph_pos, (VALUE vpos, VALUE tx), V_Subr2, DOC_char_to_glyph_pos) /*
::doc:char_to_glyph_pos::
char-to-glyph-pos [POS] [BUFFER]

From the character position POS, find its true *physical* position when
rendered.
::end:: */
{
    POS pos;
    if(!BUFFERP(tx))
	tx = VAL(curr_vw->vw_Tx);
    if(POSP(vpos))
	pos = VPOS(vpos);
    else
	pos = *get_tx_cursor(VTX(tx));
    pos.pos_Col = glyph_col(VTX(tx), pos.pos_Col, pos.pos_Line);
    return(make_lpos(&pos));
}

_PR VALUE cmd_glyph_to_char_pos(VALUE vpos, VALUE tx);
DEFUN("glyph-to-char-pos", cmd_glyph_to_char_pos, subr_glyph_to_char_pos, (VALUE vpos, VALUE tx), V_Subr2, DOC_glyph_to_char_pos) /*
::doc:glyph_to_char_pos::
glyph-to-char-pos POS [BUFFER]

For the physical position POS, find the closest matching actual character
position.
::end:: */
{
    POS pos;
    if(!BUFFERP(tx))
	tx = VAL(curr_vw->vw_Tx);
    DECLARE1(vpos, POSP);
    pos = VPOS(vpos);
    pos.pos_Col = char_col(VTX(tx), pos.pos_Col, pos.pos_Line);
    return(make_lpos(&pos));
}

_PR VALUE cmd_default_glyph_table(void);
DEFUN("default-glyph-table", cmd_default_glyph_table, subr_default_glyph_table, (void), V_Subr0, DOC_default_glyph_table) /*
::doc:default_glyph_table::
default-glyph-table

Returns the standard glyph-table.
::end:: */
{
    return(VAL(&default_glyph_table));
}

_PR VALUE cmd_make_glyph_table(VALUE src);
DEFUN("make-glyph-table", cmd_make_glyph_table, subr_make_glyph_table, (VALUE src), V_Subr1, DOC_make_glyph_table) /*
::doc:make_glyph_table::
make-glyph-table SRC

Creates a new glyph-table. If SRC is a glyph-table it will be copied, else if
SRC is a buffer that buffer's glyph-table will be copied. If SRC is nil the
default glyph-table will be copied.
::end:: */
{
    GlyphTable *newgt = mymalloc(sizeof(GlyphTable));
    if(newgt)
    {
	GlyphTable *srcgt;
	if(GLYPHTABP(src))
	    srcgt = VGLYPHTAB(src);
	else if(BUFFERP(src))
	    srcgt = VGLYPHTAB(VTX(src)->tx_GlyphTable);
	else
	    srcgt = &default_glyph_table;
	memcpy(newgt, srcgt, sizeof(GlyphTable));
	newgt->gt_Flags &= ~GTF_STATIC;
	newgt->gt_Next = gt_chain;
	gt_chain = newgt;
	data_after_gc += sizeof(GlyphTable);
	return(VAL(newgt));
    }
    return(sym_nil);
}

_PR VALUE cmd_set_glyph(VALUE gt, VALUE ch, VALUE glyph);
DEFUN("set-glyph", cmd_set_glyph, subr_set_glyph, (VALUE gt, VALUE ch, VALUE glyph), V_Subr3, DOC_set_glyph) /*
::doc:set_glyph::
set-glyph GLYPH-TABLE CHARACTER GLYPH-STRING

Make the renderer draw the string GLYPH-STRING (no more than 4 characters)
for each character CHARACTER in any buffers which use the GLYPH-TABLE.
::end:: */
{
    int glyphlen;
    TX *tx;
    DECLARE1(gt, GLYPHTABP);
    DECLARE2(ch, NUMBERP);
    DECLARE3(glyph, STRINGP);
    if((VNUM(ch) < 0) || (VNUM(ch) >= 256))
    {
	signal_arg_error(ch, 1);
	return(NULL);
    }
    glyphlen = STRING_LEN(glyph);
    if(glyphlen > 4)
    {
	signal_arg_error(glyph, 2);
	return(NULL);
    }
    VGLYPHTAB(gt)->gt_Widths[VNUM(ch)] = glyphlen;
    if(glyphlen == 0)
    {
	/* put a space in the first character */
	VGLYPHTAB(gt)->gt_Glyphs[VNUM(ch)][0] = ' ';
    }
    else
	memcpy(&VGLYPHTAB(gt)->gt_Glyphs[VNUM(ch)][0], VSTR(glyph), glyphlen);

    tx = buffer_chain;
    while(tx)
    {
	/* refresh all buffers which use this glyph table */
	if(tx->tx_GlyphTable == gt)
	    tx->tx_Flags |= TXFF_REFRESH_ALL;
	tx = tx->tx_Next;
    }

    return(sym_t);
}

_PR VALUE cmd_get_glyph(VALUE gt, VALUE ch);
DEFUN("get-glyph", cmd_get_glyph, subr_get_glyph, (VALUE gt, VALUE ch), V_Subr2, DOC_get_glyph) /*
::doc:get_glyph::
get-glyph GLYPH-TABLE CHARACTER

Return the string which is the rendered representation of CHARACTER in
GLYPH-TABLE.
::end:: */
{
    DECLARE1(gt, GLYPHTABP);
    DECLARE2(ch, NUMBERP);
    if((VNUM(ch) < 0) || (VNUM(ch) >= 256))
    {
	signal_arg_error(ch, 1);
	return(NULL);
    }
    return(string_dupn(&VGLYPHTAB(gt)->gt_Glyphs[VNUM(ch)][0],
		       VGLYPHTAB(gt)->gt_Widths[VNUM(ch)]));
}

_PR VALUE cmd_buffer_glyph_table(VALUE tx);
DEFUN("buffer-glyph-table", cmd_buffer_glyph_table, subr_buffer_glyph_table, (VALUE tx), V_Subr1, DOC_buffer_glyph_table) /*
::doc:buffer_glyph_table::
buffer-glyph-table [BUFFER]

Returns the glyph-table being used in BUFFER.
::end:: */
{
    if(!BUFFERP(tx))
	tx = VAL(curr_vw->vw_Tx);
    return(VTX(tx)->tx_GlyphTable);
}

_PR VALUE cmd_set_buffer_glyph_table(VALUE tx, VALUE gt);
DEFUN("set-buffer-glyph-table", cmd_set_buffer_glyph_table, subr_set_buffer_glyph_table, (VALUE tx, VALUE gt), V_Subr2, DOC_set_buffer_glyph_table) /*
::doc:set_buffer_glyph_table::
set-buffer-glyph-table [BUFFER] GLYPH-TABLE

Sets the glyph-table being used in BUFFER to GLYPH-TABLE.
::end:: */
{
    if(!BUFFERP(tx))
	tx = VAL(curr_vw->vw_Tx);
    DECLARE2(gt, GLYPHTABP);
    VTX(tx)->tx_GlyphTable = gt;
    VTX(tx)->tx_Flags |= TXFF_REFRESH_ALL;
    return(gt);
}

void
glyphtable_sweep(void)
{
    GlyphTable *gt = gt_chain;
    gt_chain = NULL;
    while(gt)
    {
	GlyphTable *nxt = gt->gt_Next;
	if(!GC_MARKEDP(VAL(gt)) && !(gt->gt_Flags & GTF_STATIC))
	    myfree(gt);
	else
	{
	    GC_CLR(VAL(gt));
	    gt->gt_Next = gt_chain;
	    gt_chain = gt;
	}
	gt = nxt;
    }
}

void
glyphtable_prin(VALUE strm, VALUE obj)
{
    stream_puts(strm, "#<glyph-table>", -1, FALSE);
}

void
glyphs_init(void)
{
    ADD_SUBR(subr_glyph_table_p);
    ADD_SUBR(subr_glyph_to_char_pos);
    ADD_SUBR(subr_char_to_glyph_pos);
    ADD_SUBR(subr_default_glyph_table);
    ADD_SUBR(subr_make_glyph_table);
    ADD_SUBR(subr_set_glyph);
    ADD_SUBR(subr_get_glyph);
    ADD_SUBR(subr_buffer_glyph_table);
    ADD_SUBR(subr_set_buffer_glyph_table);
}

void
glyphs_kill(void)
{
    GlyphTable *gt = gt_chain;
    while(gt)
    {
	GlyphTable *nxt = gt->gt_Next;
	if(!(gt->gt_Flags & GTF_STATIC))
	    myfree(gt);
	gt = nxt;
    }
    gt_chain = NULL;
}
