/*
 * text rendering for the framebuffer console
 * pick fonts from X11 font server or
 * use linux consolefont psf files.
 * (c) 2001 Gerd Knorr <kraxel@bytesex.org>
 */
#include "config.h"

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <linux/fb.h>

#include "fbtools.h"
#include "fs.h"

/* ------------------------------------------------------------------ */

#define BIT_ORDER       BitmapFormatBitOrderMSB
#ifdef BYTE_ORDER
#undef BYTE_ORDER
#endif
#define BYTE_ORDER      BitmapFormatByteOrderMSB
#define SCANLINE_UNIT   BitmapFormatScanlineUnit8
#define SCANLINE_PAD    BitmapFormatScanlinePad8
#define EXTENTS         BitmapFormatImageRectMin

#define SCANLINE_PAD_BYTES 1
#define GLWIDTHBYTESPADDED(bits, nBytes)                                    \
        ((nBytes) == 1 ? (((bits)  +  7) >> 3)          /* pad to 1 byte  */\
        :(nBytes) == 2 ? ((((bits) + 15) >> 3) & ~1)    /* pad to 2 bytes */\
        :(nBytes) == 4 ? ((((bits) + 31) >> 3) & ~3)    /* pad to 4 bytes */\
        :(nBytes) == 8 ? ((((bits) + 63) >> 3) & ~7)    /* pad to 8 bytes */\
        : 0)

static const unsigned fs_masktab[] = {
    (1 << 7), (1 << 6), (1 << 5), (1 << 4),
    (1 << 3), (1 << 2), (1 << 1), (1 << 0),
};

/* ------------------------------------------------------------------ */

#ifndef X_DISPLAY_MISSING
static FSServer           *svr;
#endif
static unsigned int       bpp,black,white;

static void (*setpixel)(void *ptr, unsigned int color);

static void setpixel1(void *ptr, unsigned int color)
{
    unsigned char *p = ptr;
    *p = color;
}
static void setpixel2(void *ptr, unsigned int color)
{
    unsigned short *p = ptr;
    *p = color;
}
static void setpixel3(void *ptr, unsigned int color)
{
    unsigned char *p = ptr;
    *(p++) = (color >> 16) & 0xff;
    *(p++) = (color >>  8) & 0xff;
    *(p++) =  color        & 0xff;
}
static void setpixel4(void *ptr, unsigned int color)
{
    unsigned long *p = ptr;
    *p = color;
}

int fs_init_fb(int white8)
{
    switch (fb_var.bits_per_pixel) {
    case 8:
	white = white8; black = 0; bpp = 1;
	setpixel = setpixel1;
	break;
    case 15:
    case 16:
	if (fb_var.green.length == 6)
	    white = 0xffff;
	else
	    white = 0x7fff;
	black = 0; bpp = 2;
	setpixel = setpixel2;
	break;
    case 24:
	white = 0xffffff; black = 0; bpp = fb_var.bits_per_pixel/8;
	setpixel = setpixel3;
	break;
    case 32:
	white = 0xffffff; black = 0; bpp = fb_var.bits_per_pixel/8;
	setpixel = setpixel4;
	break;
    default:
	fprintf(stderr, "Oops: %i bit/pixel ???\n",
		fb_var.bits_per_pixel);
	return -1;
    }
    return 0;
}

void fs_render_fb(unsigned char *ptr, int pitch,
		  FSXCharInfo *charInfo, unsigned char *data)
{
    int row,bit,bpr,x;

    bpr = GLWIDTHBYTESPADDED((charInfo->right - charInfo->left),
			     SCANLINE_PAD_BYTES);
    for (row = 0; row < (charInfo->ascent + charInfo->descent); row++) {
	for (x = 0, bit = 0; bit < (charInfo->right - charInfo->left); bit++) {
	    if (data[bit>>3] & fs_masktab[bit&7])
		setpixel(ptr+x,white);
	    x += bpp;
	}
	data += bpr;
	ptr += pitch;
    }
}

int fs_puts(struct fs_font *f, unsigned int x, unsigned int y,
	    unsigned char *str)
{
    unsigned char *pos,*start;
    int i,c,j,w;

    pos  = fb_mem+fb_mem_offset;
    pos += fb_fix.line_length * y;
    for (i = 0; str[i] != '\0'; i++) {
	c = str[i];
	if (NULL == f->eindex[c])
	    continue;
	/* clear with bg color */
	start = pos + x*bpp + f->fontHeader.max_bounds.descent * fb_fix.line_length;
	w = (f->eindex[c]->width+1)*bpp;
	for (j = 0; j < f->height; j++) {
	    memset(start,0,w);
	    start += fb_fix.line_length;
	}
	/* draw char */
	start = pos + x*bpp + fb_fix.line_length * (f->height-f->eindex[c]->ascent);
	fs_render_fb(start,fb_fix.line_length,f->eindex[c],f->gindex[c]);
	x += f->eindex[c]->width;
	if (x > fb_var.xres - f->width)
	    return -1;
    }
    return x;
}

int fs_textwidth(struct fs_font *f, unsigned char *str)
{
    int width = 0;
    int i,c;
    
    for (i = 0; str[i] != '\0'; i++) {
	c = str[i];
	if (NULL == f->eindex[c])
	    continue;
	width += f->eindex[c]->width;
    }
    return width;
}

void fs_render_tty(FSXCharInfo *charInfo, unsigned char *data)
{
    int bpr,row,bit,on;

    bpr = GLWIDTHBYTESPADDED((charInfo->right - charInfo->left),
			     SCANLINE_PAD_BYTES);
    for (row = 0; row < (charInfo->ascent + charInfo->descent); row++) {
	fprintf(stdout,"|");
	for (bit = 0; bit < (charInfo->right - charInfo->left); bit++) {
	    on = data[bit>>3] & fs_masktab[bit&7];
	    fprintf(stdout,"%s",on ? "##" : "  ");
	}
	fprintf(stdout,"|\n");
	data += bpr;
    }
    fprintf(stdout,"--\n");
}

/* ------------------------------------------------------------------ */

#ifndef X_DISPLAY_MISSING
/* connect to font server */
int fs_connect(char *servername)
{
    if (NULL == servername)
	servername = getenv("FONTSERVER");
    if (NULL == servername)
	servername = "unix/:7100";
    svr = FSOpenServer(servername);
    if (NULL == svr) {
	if (NULL == FSServerName(servername)) {
	    fprintf(stderr, "no font server defined\n");
	} else {
	    fprintf(stderr, "unable to open server \"%s\"\n",
		    FSServerName(servername));
	}
	return -1;
    }
    return 0;
}

/* load font from font server */
struct fs_font* fs_open(char *pattern)
{
    int              nnames = 1;
    int              available,high,low,encoding,bpr;
    char             **fonts;
    unsigned char    *glyph;
    Font             dummy;
    FSBitmapFormat   format;
    FSXCharInfo      *charInfo;
    struct fs_font   *f = NULL;
    
    if (NULL == svr) {
	fprintf(stderr,"fs: not connected\n");
	return NULL;
    }

    fonts = FSListFonts(svr, pattern, nnames, &available);
    if (0 == available) {
	fprintf(stderr,"fs: font not available [%s]\n",pattern);
	goto out;
    }
    fprintf(stderr,"using x11 font \"%s\"\n",fonts[0]);

    f = malloc(sizeof(*f));
    memset(f,0,sizeof(*f));
    f->font = FSOpenBitmapFont(svr, 0, 0, fonts[0], &dummy);
    FSFreeFontNames(fonts);
    if (0 == f->font)
	goto out;
    
    FSQueryXInfo(svr,f->font,&f->fontHeader, &f->propInfo,
		 &f->propOffsets, &f->propData);
    format = BYTE_ORDER | BIT_ORDER | SCANLINE_UNIT | SCANLINE_PAD | EXTENTS;
    FSQueryXExtents16(svr, f->font, True, (FSChar2b *) 0, 0, &f->extents);
    FSQueryXBitmaps16(svr, f->font, format, True, (FSChar2b *) 0, 0,
		      &f->offsets, &f->glyphs);

    f->maxenc = (f->fontHeader.char_range.max_char.high+1) << 8;
    f->width  = f->fontHeader.max_bounds.right - f->fontHeader.min_bounds.left;
    f->height = f->fontHeader.max_bounds.ascent + f->fontHeader.max_bounds.descent;
    f->eindex = malloc(f->maxenc * sizeof(FSXCharInfo*));
    f->gindex = malloc(f->maxenc * sizeof(unsigned char*));
    memset(f->eindex,0,f->maxenc * sizeof(FSXCharInfo*));
    memset(f->gindex,0,f->maxenc * sizeof(unsigned char*));

    glyph    = f->glyphs;
    charInfo = f->extents;
    for (high  = f->fontHeader.char_range.min_char.high;
	 high <= f->fontHeader.char_range.max_char.high;
	 high++) {
	for (low  = f->fontHeader.char_range.min_char.low;
	     low <= f->fontHeader.char_range.max_char.low;
	     low++) {
	    bpr = GLWIDTHBYTESPADDED((charInfo->right - charInfo->left),
				     SCANLINE_PAD_BYTES);
	    encoding = (high<<8) + low;
#ifdef TTY
	    fprintf(stdout,"e=0x%x | w=%d  l=%d r=%d  |  a=%d d=%d\n",
		    encoding,charInfo->width,charInfo->left,
		    charInfo->right,charInfo->ascent,charInfo->descent);
#endif
	    if ((charInfo->width != 0) || (charInfo->right != charInfo->left)) {
		f->gindex[encoding] = glyph;
		f->eindex[encoding] = charInfo;
#ifdef TTY
		fs_render_tty(f->eindex[encoding],
			      f->gindex[encoding]);
#endif
	    }
	    glyph += (charInfo->descent + charInfo->ascent) * bpr;
	    charInfo++;
	}
    }
    return f;

 out:
    if (f) 
	fs_free(f);
    return NULL;
}
#endif

void fs_free(struct fs_font *f)
{
    if (f->gindex)
	free(f->gindex);
#if 0
    if (f->extents)
	FSFree((char *) f->extents);
    if (f->offsets)
	FSFree((char *) f->offsets);
    if (f->propOffsets)
	FSFree((char *) (f->propOffsets));
    if (f->propData)
	FSFree((char *) (f->propData));
#endif
#if 0 /* FIXME */
    if (f->glyphs)
	FSFree((char *) f->glyphs);
#endif
    free(f);
}

/* ------------------------------------------------------------------ */
/* load console font file                                             */

static char *default_font[] = {
    /* why the heck every f*cking distribution picks another
       location for these fonts ??? */
    "/usr/share/consolefonts/lat1-16.psf",
    "/usr/share/consolefonts/lat1-16.psf.gz",
    "/usr/share/consolefonts/lat1-16.psfu.gz",
    "/usr/share/kbd/consolefonts/lat1-16.psf",
    "/usr/share/kbd/consolefonts/lat1-16.psf.gz",
    "/usr/share/kbd/consolefonts/lat1-16.psfu.gz",
    "/usr/lib/kbd/consolefonts/lat1-16.psf",
    "/usr/lib/kbd/consolefonts/lat1-16.psf.gz",
    "/usr/lib/kbd/consolefonts/lat1-16.psfu.gz",
    "/lib/kbd/consolefonts/lat1-16.psf",
    "/lib/kbd/consolefonts/lat1-16.psf.gz",
    "/lib/kbd/consolefonts/lat1-16.psfu.gz",
    NULL
};

struct fs_font* fs_consolefont(char **filename)
{
    int  i;
    char *h,command[256];
    struct fs_font *f = NULL;
    FILE *fp;

    if (NULL == filename)
	filename = default_font;

    for(i = 0; filename[i] != NULL; i++) {
	if (-1 == access(filename[i],R_OK))
	    continue;
	break;
    }
    if (NULL == filename[i]) {
	fprintf(stderr,"can't find console font file\n");
	return NULL;
    }

    h = filename[i]+strlen(filename[i])-3;
    if (0 == strcmp(h,".gz")) {
	sprintf(command,"zcat %s",filename[i]);
	fp = popen(command,"r");
    } else {
	fp = fopen(filename[i], "r");
    }
    if (NULL == fp) {
	fprintf(stderr,"can't open %s: %s\n",filename[i],strerror(errno));
	return NULL;
    }

    if (fgetc(fp) != 0x36 ||
	fgetc(fp) != 0x04) {
	fprintf(stderr,"can't use font %s\n",filename[i]);
	return NULL;
    }
    fprintf(stderr,"using linux console font \"%s\"\n",filename[i]);

    f = malloc(sizeof(*f));
    memset(f,0,sizeof(*f));

    fgetc(fp);
    f->maxenc = 256;
    f->width  = 8;
    f->height = fgetc(fp);
    f->fontHeader.min_bounds.left    = 0;
    f->fontHeader.max_bounds.right   = f->width;
    f->fontHeader.max_bounds.descent = 0;
    f->fontHeader.max_bounds.ascent  = f->height;

    f->glyphs  = malloc(f->height * 256);
    f->extents = malloc(sizeof(FSXCharInfo)*256);
    fread(f->glyphs, 256, f->height, fp);
    fclose(fp);

    f->eindex  = malloc(sizeof(FSXCharInfo*)   * 256);
    f->gindex  = malloc(sizeof(unsigned char*) * 256);
    for (i = 0; i < 256; i++) {
	f->eindex[i] = f->extents +i;
	f->gindex[i] = f->glyphs  +i * f->height;
	f->eindex[i]->left    = 0;
	f->eindex[i]->right   = 7;
	f->eindex[i]->width   = 8;
	f->eindex[i]->descent = 0;
	f->eindex[i]->ascent  = f->height;
    }
    return f;
}


#ifdef TESTING
/* ------------------------------------------------------------------ */
/* for testing                                                        */

int debug;

/* list fonts */
int fs_ls(char *pattern)
{
    int    nnames = 16;
    int    available,i;
    char   **fonts;

    if (NULL == svr) {
	fprintf(stderr,"fs: not connected\n");
	return -1;
    }

    fonts = FSListFonts(svr, pattern, nnames, &available);
    while (nnames <= available) {
	nnames *= 2;
	FSFreeFontNames(fonts);
	fonts = FSListFonts(svr, pattern, nnames, &available);
    }
    for (i = 0; i < available; i++) {
	fprintf(stderr,"%s\n",fonts[i]);
    }
    FSFreeFontNames(fonts);
    return 0;
}

void dump_charset(struct fs_font *f)
{
    unsigned char *pos;
    int c,x,y;

    x = 0, y = 0;
    for (c = 0; c < f->maxenc; c++) {
	if (NULL == f->eindex[c])
	    continue;
	pos  = fb_mem+fb_mem_offset;
	pos += fb_fix.line_length * (y+f->height-f->eindex[c]->ascent);
	pos += x*bpp;
	fs_render_fb(pos,fb_fix.line_length,f->eindex[c],f->gindex[c]);
	x += f->eindex[c]->right-f->eindex[c]->left+1;
	if (x > fb_var.xres - f->width) {
	    x = 0;
	    y += f->height+1;
	}
	if (y > fb_var.yres - f->height)
	    break;
    }
}

int main(int argc, char *argv[])
{
    struct fs_font *f = NULL;
    unsigned char dummy[42];
    int fd;

    if (argc < 2) {
	fprintf(stderr,"missing arg\n");
	exit(1);
    }

    /* try font server */
    if (-1 != fs_connect(NULL)) {
	fs_ls(argv[1]);
	f = fs_open(argv[1]);
	if (NULL == f)
	    fprintf(stderr,"no such font\n");
    }

    /* try console font */
    if (NULL == f)
	f = fs_consolefont(NULL);
    if (NULL == f)
	exit(1);
	
#ifdef TTY
    exit(1);
#endif

    fd = fb_init(NULL, NULL, 0);
    fb_cleanup_fork();
    fb_switch_init();
    fs_init_fb();

    if (argc < 3) {
	dump_charset(f);
    } else {
	fs_puts(f,0,0,argv[2]);
    }
    fgets(dummy,42,stdin);
    
    return 0;
}
#endif
