/**
 ** buildfnt.c ---- allocate and fill a font descriptor
 **
 ** Copyright (c) 1995 Csaba Biegl, 820 Stirrup Dr, Nashville, TN 37221
 ** [e-mail: csaba@vuse.vanderbilt.edu]
 **
 ** This file is part of the GRX graphics library.
 **
 ** The GRX graphics library is free software; you can redistribute it
 ** and/or modify it under some conditions; see the "copying.grx" file
 ** for details.
 **
 ** This library 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.
 **
 ** 220715 M.Alvarez, fonts can be sparse
 **
 **/

#include "libgrx.h"
#include "grfontdv.h"
#include "arith.h"

/*
 * amounts for various font conversions
 */
#define BOLDWDT(width)                  (((width)  + 3) / 10)
#define ITALWDT(height)                 (((height) + 1) / 7)
#define PROPGAP(width)                  (((width)  / 6) ? ((width) / 6) : 1)

/*
 * Font conversion option structure
 */
typedef struct {
    int     oldhgt;                     /* height of the source font */
    int     newhgt;                     /* height of the created font */
    int     boldwdt;                    /* shift and OR in X dir by this much */
    int     italwdt;                    /* tilt font by this much */
    int     dofix;                      /* convert to fixed width */
    int     fixwdt;                     /* width of the fixed cvt font */
    int     doprop;                     /* convert to proportional width */
    int     propgap;                    /* zero edge pixels for prop cvt */
} conv;

#ifdef DEBUG
static void bdump(char *hd,unsigned char *bmp,int w,int h,int pitch)
{
    char *s;
    DBGPRINTF(DBG_FONT, ("%s (%dx%d):\n",hd,w,h));
    s = malloc((size_t) (w + 1));
    if(s == NULL) DBGPRINTF(DBG_FONT, ("no memory to show the char\n"));
    else {
        while(--h >= 0) {
            int x;
            for(x = 0; x < w; x++) s[x] = bmp[x] ? '#' : '.';
            s[x] = '\0';
            DBGPRINTF(DBG_FONT, ("%s\n", s));
            bmp += pitch;
        }
        free(s);
    }
}
#define BDUMP(hd,bmp,w,h,pitch) bdump(hd,bmp,w,h,pitch)
#else
#define BDUMP(hd,bmp,w,h,pitch)
#endif

static int cvtbitmap(int oldw,int neww,conv *c,char *bmp)
{
    int wofs,i,j,x,y,w,h;
    unsigned char *work_base;

    wofs = umax(oldw,neww) + c->boldwdt + c->italwdt + c->propgap;
    wofs = umax(wofs,c->fixwdt);
    wofs = (wofs + 15) & ~3;
    i    = wofs * (umax(c->newhgt,c->oldhgt) + 2);
    work_base = malloc((size_t)i);
    if(work_base) {
        unsigned char *work = work_base;
        memset(work,0,i);
        work += wofs;
        w = neww - c->boldwdt - c->italwdt;
        h = c->newhgt;
        if((w == oldw) && (h == c->oldhgt)) {
            /* No resizing is needed */
            for(y = 0; y < h; y++) {
                i = ((w + 7) & ~7) * y;
                j = wofs * y;
                for(x = 0; x < w; x++,i++,j++) {
                    work[j] = bmp[i >> 3] & (0x80 >> (i & 7));
                }
            }
            BDUMP("after unpacking",work,w,h,wofs);
        }
        else {
            /* Resize the bitmap */
            /* The algorithm first looks for rectangles of '1' pixels */
            /* and then maps these onto the coordinate space of the */
            /* resized bitmap. This seems to work better than simple */
            /* pixel sampling. (No feature loss) */
            for(y = 0; y < c->oldhgt; y++) {
                unsigned int pos = 0,ones = 0;
                i = ((oldw + 7) & ~7) * y;
                for(x = 0; x < oldw; x++,i++) {
                    if(bmp[i >> 3] & (0x80 >> (i & 7))) {
                        if(!ones) pos = x;
                        ones++;
                        if(x != (oldw - 1)) continue;
                    }
                    if(ones > 0) {
                        unsigned int x1 = urscale(pos,w,oldw);
                        unsigned int x2 = urscale((pos + ones),w,oldw);
                        unsigned int y1 = urscale(y,h,c->oldhgt);
                        unsigned int y2 = urscale((y + 1),h,c->oldhgt);
                        do {
                            unsigned int xx = x1;
                            j = (y1 * wofs) + xx;
                            do {
                                work[j++] = 1;
                            } while(++xx < x2);
                        } while(++y1 < y2);
                        ones = 0;
                    }
                }
            }
            BDUMP("after resize",work,w,h,wofs);
        }
        /* bold conversion */
        if(c->boldwdt > 0) {
            for(y = 0; y < h; y++) {
                unsigned char *row = &work[wofs * y];
                unsigned char *p1  = &row[w];
                unsigned char *p2  = &row[w + c->boldwdt];
                while(p1 > row) *--p2 |= *--p1;
            }
            w += c->boldwdt;
            BDUMP("after boldify",work,w,h,wofs);
        }
        /* italic conversion */
        if(c->italwdt > 0) {
            int ymax = h - 1;
            int yrnd = ymax >> 1;
            for(y = 0; y < h; y++) {
                int   tilt = ((c->italwdt * (ymax - y)) + yrnd) / ymax;
                unsigned char *row = &work[wofs * y];
                unsigned char *p1  = &row[w];
                unsigned char *p2  = &row[w + tilt];
                while(p1 > row) *--p2 = *--p1;
                while(p2 > row) *--p2 = 0;
            }
            w += c->italwdt;
            BDUMP("after italicize",work,w,h,wofs);
        }
        x = 0;
        /* proportional or fixed width adjustment */
        if(c->dofix || c->doprop) {
            int minx = w;
            int maxx = 0;
            for(y = 0; y < h; y++) {
                i = y * wofs;
                for(j = 0; j < w; j++,i++) {
                    if(work[i] && (j < minx)) minx = j;
                    if(work[i] && (j > maxx)) maxx = j;
                }
            }
            if(minx > maxx) {
                minx = 0;
                maxx = w - 1;
            }
            if(c->dofix) {
                neww = c->fixwdt;
                w    = maxx - minx + 1;
                x    = (neww - w) >> 1;
            }
            if(c->doprop) {
                w    = maxx - minx + 1;
                x    = minx - (c->propgap >> 1);
                neww = w + c->propgap;
            }
        }
        for(y = 0,i = (neww + 7) >> 3; y < h; y++) {
            char  *bp = &bmp[y * i];
            unsigned char *wp = &work[y * wofs];
            int  xpos = x;
            memset(bp,0,i);
            for(j = 0; j < neww; j++,xpos++) {
                if(wp[xpos]) bp[j >> 3] |= (0x80 >> (j & 7));
            }
        }
        free(work_base);
    }
    return(neww);
}

GrFont *_GrBuildFont(const GrFontHeader *h, int  cvt, int  wdt, int  hgt,
                     int  cmin, int  cmax, int  (*charwdt)(int chr),
                     int  (*bitmap)(int chr,int w,int h,char *buffer),
                     int  scaled)
{
    GrFont *f    = NULL;
    unsigned int  fprop  = h->proportional;
    unsigned int  chrcv  = GR_FONTCVT_NONE;
    unsigned int  bmpcv  = GR_FONTCVT_NONE;
    unsigned long totwdt = 0L;
    unsigned long bmplen = 0L;
    unsigned int  bmpofs = 0;
    unsigned int  numch,i,chr;
    char  *bmp = NULL;
    conv  cv;

    sttzero(&cv);
    if(cvt & GR_FONTCVT_SKIPCHARS) {
        unsigned int lastfntchr = h->minchar + h->numchars - 1;
        cmin = umin(umax(cmin,h->minchar),lastfntchr);
        cmax = umin(umax(cmax,h->minchar),lastfntchr);
        if(cmin > cmax) goto error;
        if((unsigned int)cmin > h->minchar) chrcv = GR_FONTCVT_SKIPCHARS;
        if((unsigned int)cmax < lastfntchr) chrcv = GR_FONTCVT_SKIPCHARS;
    }
    else {
        cmin = h->minchar;
        cmax = h->minchar + h->numchars - 1;
    }
    if(cvt & GR_FONTCVT_RESIZE) {
        if((unsigned int)(wdt = imax(2,wdt)) != h->width)  bmpcv=GR_FONTCVT_RESIZE;
        if((unsigned int)(hgt = imax(2,hgt)) != h->height) bmpcv=GR_FONTCVT_RESIZE;
    }
    else {
        wdt = h->width;
        hgt = h->height;
    }
    numch = cmax - cmin + 1;
    i = sizeof(GrFont) + ((numch - 1) * sizeof(GrFontChrInfo));
    f = malloc(i);
    if(!f) goto error;
    memset(f,0,i);
    f->h.name   = malloc(strlen(h->name)   + 1);
    f->h.family = malloc(strlen(h->family) + 1);
    if(!f->h.name || !f->h.family) goto error;
    strcpy(f->h.name  ,h->name);
    strcpy(f->h.family,h->family);
    f->minwidth = 0x7fff;
    f->maxwidth = 0;
    for(chr = cmin,i = 0; i < numch; chr++,i++) {
        int  oldw = (*charwdt)(chr);
        if(oldw < 0) goto error;
        f->chrinfo[i].width = oldw;
        if (oldw == 0) {
            if (!h->sparse) goto error;
            f->chrinfo[i].offset = 0;
        } else {
            unsigned int neww = urscale(oldw,wdt,h->width);
            if(f->minwidth > neww) f->minwidth = neww;
            if(f->maxwidth < neww) f->maxwidth = neww;
            bmplen += ((neww + 7) >> 3) * hgt;
        }
    }
    cv.oldhgt = scaled ? hgt : h->height;
    cv.newhgt = hgt;
    if(cvt & GR_FONTCVT_BOLDIFY) {
        cv.boldwdt = BOLDWDT(wdt);
        cv.boldwdt = imin(cv.boldwdt,(f->minwidth - 1));
        cv.boldwdt = imax(cv.boldwdt,0);
        if(cv.boldwdt > 0) bmpcv |= GR_FONTCVT_BOLDIFY;
    }
    if(cvt & GR_FONTCVT_ITALICIZE) {
        cv.italwdt = ITALWDT(hgt);
        cv.italwdt = imin(cv.italwdt,(f->minwidth - cv.boldwdt - 1));
        cv.italwdt = imax(cv.italwdt,0);
        if(cv.italwdt > 0) bmpcv |= GR_FONTCVT_ITALICIZE;
    }
    if((cvt & GR_FONTCVT_FIXIFY) && fprop) {
        bmpcv     |= GR_FONTCVT_FIXIFY;
        cv.fixwdt  = f->maxwidth;
        bmplen     = umul32((hgt * ((cv.fixwdt + 7) >> 3)),numch);
        cv.dofix   = TRUE;
        fprop      = FALSE;
    }
    if((cvt & GR_FONTCVT_PROPORTION) && !fprop) {
        bmpcv     |= GR_FONTCVT_PROPORTION;
        cv.propgap = imax(0,(PROPGAP(wdt) - cv.italwdt));
        bmplen     = umul32((hgt * ((wdt + cv.propgap + 7) >> 3)),numch);
        cv.doprop  = TRUE;
        fprop      = TRUE;
    }
    if((unsigned long)(unsigned int)bmplen != bmplen) goto error;
    f->bitmap = malloc(bmplen);
    if(!f->bitmap) goto error;
    memset(f->bitmap,0,(unsigned int)bmplen);
    i = f->maxwidth;
    if(h->width > (unsigned int)wdt) i = urscale(i,h->width,wdt);
    i = ((i + 31) >> 3) * umax(hgt,h->height);
    bmp = malloc((size_t)i);
    if(!bmp) goto error;
    f->minwidth = 0x7fff;
    f->maxwidth = 0;
    for(chr = cmin,i = 0; i < numch; chr++,i++) {
        unsigned int oldw = f->chrinfo[i].width;
        if (oldw == 0) continue;  // a sparse font
        unsigned int neww = imax(urscale(oldw,wdt,h->width),1);
        unsigned int size;
        if(scaled) {
            unsigned int raww = neww - cv.boldwdt - cv.italwdt;
            if(!(*bitmap)(chr,raww,hgt,bmp)) goto error;
            if(bmpcv & ~GR_FONTCVT_RESIZE) neww = cvtbitmap(oldw,raww,&cv,bmp);
        }
        else {
            if(!(*bitmap)(chr,oldw,h->height,bmp)) goto error;
            if(bmpcv) neww = cvtbitmap(oldw,neww,&cv,bmp);
        }
        if(f->minwidth > neww) f->minwidth = neww;
        if(f->maxwidth < neww) f->maxwidth = neww;
        size = ((neww + 7) >> 3) * hgt;
        memcpy(&f->bitmap[bmpofs],bmp,size);
        f->chrinfo[i].width  = neww;
        f->chrinfo[i].offset = bmpofs;
        bmpofs += size;
        totwdt += neww;
    }
    f->h.proportional = fprop;
    f->h.scalable     = h->scalable;
    f->h.preloaded    = FALSE;
    f->h.modified     = h->modified | chrcv | bmpcv;
    f->h.minchar      = cmin;
    f->h.numchars     = numch;
    f->h.width        = (cv.dofix || cv.doprop) ? (unsigned int)(totwdt / numch) : wdt;
    f->h.height       = hgt;
    f->h.baseline     = urscale(h->baseline,hgt,h->height);
    f->h.ulpos        = urscale(h->ulpos,   hgt,h->height);
    f->h.ulheight     = urscale(h->ulheight,hgt,h->height);
    f->h.encoding     = h->encoding;
    f->h.sparse       = h->sparse;
    f->h.usedefg      = h->usedefg;
    f->h.unused1      = 0;
    f->h.unused2      = 0;
    f->h.defglyph     = h->defglyph;
    goto done;
    error:
    if(f) {
        if(f->h.name)   free(f->h.name);
        if(f->h.family) free(f->h.family);
        if(f->bitmap)   free(f->bitmap);
        free(f);
        f = NULL;
    }
    done:
    if (bmp) free(bmp);
    return(f);
}

