/*
  pktopbm, adapted from "pktopx in C by Tomas Rokicki" by AJCD 1/8/90
  1998-09-22: jcn <janneke@gnu.org>
     - lots of bugfixes:
     * always read x/y offset bytes (3x)
     * reset bmx, bmy to defaults for each char
     * fix bitmap y placement of dynamically packed char
     * skip char early if no output file allocated
     - added debug output
  
  compile with: cc -lpbm -o pktopbm pktopbm.c
  */

#include <stdio.h>
#include <string.h>

#include "pm_c_util.h"
#include "nstring.h"
#include "pbm.h"

#define NAMELENGTH 80
#define MAXROWWIDTH 3200
#define MAXPKCHAR 256

typedef int integer ;
typedef unsigned char quarterword ;
typedef char boolean ;
typedef quarterword eightbits ;

static FILE *pkfile ;
static char pkname[NAMELENGTH+1] ;
static integer pktopbm_pkloc = 0;
static char *filename[MAXPKCHAR] ;
static bit **bitmap = NULL ;
static integer dynf ;
static eightbits inputbyte ;
static eightbits bitweight ;
static integer repeatcount ;
static integer flagbyte ;
static integer debug=0;

#define dprintf(s,d) if (debug) printf(s,d)
#define dprintf0(s) if (debug) printf(s)

/* add a suffix to a filename in an allocated space */
static void
pktopbm_add_suffix(char *       const name, 
                   const char * const suffix) {

    char * const slash = strrchr(name, '/');
    char * const dot   = strrchr(name, '.');
    
    if ((dot && slash ? dot < slash : !dot) && !streq(name, "-"))
        strcat(name, suffix);
}



/* get a byte from the PK file */
static eightbits 
pktopbm_pkbyte(void) {
   pktopbm_pkloc++ ;
   return(getc(pkfile)) ;
}



/* get a 16-bit half word from the PK file */
static integer 
get16(void) {
   integer const a = pktopbm_pkbyte() ;
   return((a<<8) + pktopbm_pkbyte()) ;
}



/* get a 32-bit word from the PK file */
static integer get32(void) {
    integer a;
    a = get16() ;
    if (a > 32767) a -= 65536 ;
    return((a<<16) + get16()) ;
}



/* get a nibble from current input byte, or new byte if no current byte */
static integer 
getnyb(void) {
    eightbits temp;
    if (bitweight == 0) {
        inputbyte = pktopbm_pkbyte() ;
        bitweight = 16 ;
    }
    temp = inputbyte / bitweight ;
    inputbyte -= temp * bitweight ;
    bitweight >>= 4 ;
    return(temp) ;
}



/* get a bit from the current input byte, or a new byte if no current byte */
static bool
getbit(void) {
    bool temp ;
    bitweight >>= 1 ;
    if (bitweight == 0) {
        inputbyte = pktopbm_pkbyte() ;
        bitweight = 128 ;
    }
    temp = (inputbyte >= bitweight) ;
    if (temp) inputbyte -= bitweight ;
    return(temp) ;
}



/* unpack a dynamically packed number. dynf is dynamic packing threshold  */
static integer 
pkpackednum(void) {
    integer i, j ;
    i = getnyb() ;
    if (i == 0) {           /* large run count, >= 3 nibbles */
        do {
            j = getnyb() ;          /* count extra nibbles */
            i++ ;
        } while (j == 0) ;
        while (i > 0) {
            j = (j<<4) + getnyb() ; /* add extra nibbles */
            i-- ;
        }
        return (j - 15 +((13 - dynf)<<4) + dynf) ;
    } else if (i <= dynf) return (i) ;  /* number > 0 and <= dynf */
    else if (i < 14) return (((i - dynf - 1)<<4) + getnyb() + dynf + 1) ;
    else {
        if (i == 14) repeatcount = pkpackednum() ;  /* get repeat count */
        else repeatcount = 1 ;      /* value 15 indicates repeat count 1 */
        return(pkpackednum()) ;
    }
}



/* skip specials in PK files, inserted by Metafont or some other program */
static void
skipspecials(void) {
    integer i, j;
    do {
        flagbyte = pktopbm_pkbyte() ;
        if (flagbyte >= 240)
            switch(flagbyte) {
            case 240:           /* specials of size 1-4 bytes */
            case 241:
            case 242:
            case 243:
                i = 0 ;
                for (j = 240 ; j <= flagbyte ; ++j) 
                    i = (i<<8) + pktopbm_pkbyte() ;
                for (j = 1 ; j <= i ; ++j) 
                    pktopbm_pkbyte() ;  /* ignore special */
                break ;
            case 244:           /* no-op, parameters to specials */
                get32() ;
            case 245:           /* start of postamble */
            case 246:           /* no-op */
                break ;
            case 247:           /* pre-amble in wrong place */
            case 248:
            case 249:
            case 250:
            case 251:
            case 252:
            case 253:
            case 254:
            case 255:
                pm_error("unexpected flag byte %d", flagbyte) ;
            }
    } while (!(flagbyte < 240 || flagbyte == 245)) ;
}



/* ignore character packet */
static void
ignorechar(integer const car, 
           integer const endofpacket) {

   while (pktopbm_pkloc != endofpacket) pktopbm_pkbyte() ;
   if (car < 0 || car >= MAXPKCHAR)
      pm_message("Character %d out of range", car) ;
   skipspecials() ;
}



int
main(int argc, char *argv[]) {
    integer x;
    integer endofpacket ;
    boolean turnon ;
    integer i, j;
    integer car ;
    integer bmx=0, bmy=0;
    integer set_bmx=0, set_bmy=0;
    bit row[MAXROWWIDTH+1] ;
    const char * const usage = 
        "pkfile[.pk] [-d] [[-x width] [-y height] [-c num] pbmfile]...";
   
    pbm_init(&argc, argv);
    for (i = 0 ; i < MAXPKCHAR ; i ++) filename[i] = NULL ;

    pm_message("This is PKtoPBM, version 2.5") ;

    if (--argc < 1) pm_usage(usage) ;

    ++argv;
    if(strlen(*argv) + 4 > NAMELENGTH)
        pm_error("pkname is too long");
    strcpy(pkname, *argv) ;
    pktopbm_add_suffix(pkname, ".pk") ;

    car = 0 ;
    /* urg: use getopt */
    while (++argv, --argc) {
        if (argv[0][0] == '-' && argv[0][1])
            switch (argv[0][1]) {
            case 'X':
            case 'x':
                if (argv[0][2]) bmx = atoi(*argv+2) ;
                else if (++argv, --argc) set_bmx = atoi(*argv) ;
                else pm_usage(usage) ;
                continue ;
            case 'Y':
            case 'y':
                if (argv[0][2]) bmy = atoi(*argv+2) ;
                else if (++argv, --argc) set_bmy = atoi(*argv) ;
                else pm_usage(usage) ;
                continue ;
            case 'C':
            case 'c':
                if (argv[0][2]) car = atoi(*argv+2) ;
                else if (++argv, --argc) car = atoi(*argv) ;
                else pm_usage(usage) ;
                break ;
            case 'd':
                debug=1;
                break ;
            default:
                pm_usage(usage) ;
            } else if (car < 0 || car >= MAXPKCHAR) {
                pm_error("character must be in range 0 to %d (-c)", 
                         MAXPKCHAR-1) ;
            } else filename[car++] = *argv ;
    }

    pkfile = pm_openr(pkname);
    if (pktopbm_pkbyte() != 247)
        pm_error("bad PK file (pre command missing)") ;
    if (pktopbm_pkbyte() != 89)
        pm_error("wrong version of packed file") ;
    j = pktopbm_pkbyte() ;              /* get header comment size */
    for (i = 1 ; i <= j ; i ++) pktopbm_pkbyte() ;  /* ignore header comment */
    get32() ;                   /* ignore designsize */
    get32() ;                   /* ignore checksum */
    if (get32() != get32())         /* h & v pixels per point */
        pm_message("Warning: aspect ratio not 1:1") ;
    skipspecials() ;
    while (flagbyte != 245) {           /* not at postamble */
        integer cheight, cwidth ;
        integer xoffs=0, yoffs=0;
        FILE *ofp;

        bmx=set_bmx;
        bmy=set_bmy;
        dynf = (flagbyte>>4) ;          /* get dynamic packing value */
        flagbyte &= 15 ;
        turnon = (flagbyte >= 8) ;      /* black or white initially? */
        if (turnon) flagbyte &= 7 ;     /* long or short form */
        if (flagbyte == 7) {            /* long form preamble */
            integer packetlength = get32() ;    /* character packet length */
            car = get32() ;         /* character number */
            endofpacket = packetlength + pktopbm_pkloc;
                /* calculate end of packet */
            if ((car >= MAXPKCHAR) || !filename[car]) {
                ignorechar(car, endofpacket);
                continue;
            }
            dprintf0 ("flagbyte7\n");
            dprintf ("car: %d\n", car);
            get32() ;               /* ignore tfmwidth */
            x=get32() ;             /* ignore horiz escapement */
            x=get32() ;             /* ignore vert escapement */
            dprintf ("horiz esc %d\n", x);
            dprintf ("vert esc %d\n", x);
            cwidth = get32() ;          /* bounding box width */
            cheight = get32() ;         /* bounding box height */
            dprintf ("cwidth %d\n", cwidth);
            dprintf ("cheight %d\n", cheight);
            if (cwidth < 0 || cheight < 0 || 
                cwidth > 65535 || cheight > 65535) {
                ignorechar(car, endofpacket);
                continue;
            }
            xoffs= get32() ;              /* horiz offset */
            yoffs= get32() ;              /* vert offset */
            dprintf ("xoffs %d\n", xoffs);
            dprintf ("yoffs %d\n", yoffs);
        } else if (flagbyte > 3) {      /* extended short form */
            integer packetlength = ((flagbyte - 4)<<16) + get16() ;
            /* packet length */
            car = pktopbm_pkbyte() ;            /* char number */
            endofpacket = packetlength + pktopbm_pkloc ; 
                /* calculate end of packet */
            if ((car >= MAXPKCHAR) || !filename[car]) {
                ignorechar(car, endofpacket);
                continue;
            }
            dprintf0 ("flagbyte>3\n");
            dprintf ("car: %d\n", car);
            pktopbm_pkbyte() ;              /* ignore tfmwidth (3 bytes) */
            get16() ;               /* ignore tfmwidth (3 bytes) */
            get16() ;               /* ignore horiz escapement */
            cwidth = get16() ;          /* bounding box width */
            cheight = get16() ;         /* bounding box height */
            dprintf ("cwidth %d\n", cwidth);
            dprintf ("cheight %d\n", cheight);
            xoffs=get16();                         /* horiz offset */
            if (xoffs >= 32768)
                xoffs-= 65536;
            yoffs=get16();                         /* vert offset */
            if (yoffs >= 32768)
                yoffs-= 65536;
            dprintf ("xoffs %d\n", xoffs);
            dprintf ("yoffs %d\n", yoffs);
        } else {                    /* short form preamble */
            integer packetlength = (flagbyte<<8) + pktopbm_pkbyte() ;
            /* packet length */
            car = pktopbm_pkbyte() ;            /* char number */
            endofpacket = packetlength + pktopbm_pkloc ;    
                /* calculate end of packet */
            if ((car >= MAXPKCHAR) || !filename[car]) {
                ignorechar(car, endofpacket);
                continue;
            }
            dprintf0 ("flagbyte<=3\n");
            dprintf ("car: %d\n", car);
            pktopbm_pkbyte() ;          /* ignore tfmwidth (3 bytes) */
            get16() ;               /* ignore tfmwidth (3 bytes) */
            x = pktopbm_pkbyte() ;  /* ignore horiz escapement */
            dprintf ("horiz esc %d\n", x);
            cwidth = pktopbm_pkbyte() ;            /* bounding box width */
            cheight = pktopbm_pkbyte() ;           /* bounding box height */
            dprintf ("cwidth %d\n", cwidth);
            dprintf ("cheight %d\n", cheight);
            xoffs=pktopbm_pkbyte ();               /* horiz offset */
            if (xoffs >= 128)
                xoffs-=256;
            yoffs=pktopbm_pkbyte ();               /* vert offset */
            if (yoffs >= 128)
                yoffs-=256;
            dprintf ("xoffs %d\n", xoffs);
            dprintf ("yoffs %d\n", yoffs);
        }
        if (filename[car]) {
            if (!bmx) bmx= cwidth;
            if (!bmy) bmy= cheight;
            bitmap = pbm_allocarray(bmx, bmy) ;
            if (bitmap == NULL)
                pm_error("out of memory allocating bitmap") ;
        } else {
            ignorechar(car, endofpacket);
            continue;
        }
        bitweight = 0 ;
        for (i = 0 ; i < bmy ; i ++)           /* make it blank */
            for (j = 0 ; j < bmx ; j ++)
                bitmap[i][j]= PBM_WHITE;
        if (dynf == 14) {               /* bitmapped character */
            dprintf ("bmy: %d\n ", bmy);
            dprintf ("y: %d\n ", bmy-yoffs-1);
            for (i = 0 ; i < cheight ; i ++) {
                int yi= i+(bmy-yoffs-1);
                for (j = 0 ; j < cwidth ; j ++) {
                    int xj= j-xoffs;
                    if (getbit() && 0<=xj && xj<bmx && 0<=yi && yi<bmy)
                        bitmap[yi][xj] = PBM_BLACK ;
                }
            }
        } else {                    /* dynamically packed char */
            integer rowsleft = cheight ;
            integer hbit = cwidth ;
            integer rp = 0;
            repeatcount = 0 ;
            dprintf ("bmy: %d\n ", bmy);
            dprintf ("y: %d\n", cheight-rowsleft+(bmy-2*yoffs-1));
            while (rowsleft > 0) {
                integer count = pkpackednum() ; /* get current color count */
                while (count > 0) {
                    if (count < hbit) {     /* doesn't extend past row */
                        hbit -= count ;
                        while (count--)
                            row[rp++] = turnon ? PBM_BLACK : PBM_WHITE;
                    } else {                /* reaches end of row */
                        count -= hbit ;
                        while (hbit--)
                            row[rp++] = turnon ? PBM_BLACK : PBM_WHITE;
                        for (i = 0; i <= repeatcount; i++) {  /* fill row */
                            int yi= i+cheight-rowsleft-1;
                            if (0<=yi && yi < bmy)
                                for (j = 0; j < cwidth; j++) {
                                    int xj= j-xoffs;
                                    if (0<=xj && xj<bmx)
                                        bitmap[yi][xj] = row[j] ;
                                }
                        }
                        rowsleft -= repeatcount + 1;
                        repeatcount = rp = 0 ;
                        hbit = cwidth ;
                    }
                }
                turnon = !turnon ;
            }
            if (rowsleft != 0 || hbit != cwidth)
                pm_error("bad pk file (more bits than required)") ;
        }
        if (endofpacket != pktopbm_pkloc)
            pm_error("bad pk file (bad packet length)") ;

        ofp = pm_openw(filename[car]);
        filename[car] = NULL;
        pbm_writepbm(ofp, bitmap, bmx, bmy, 0) ;
        pbm_freearray(bitmap, bmy) ;
        pm_close(ofp) ;
        skipspecials() ;
    }
    while (! feof(pkfile)) pktopbm_pkbyte() ;       /* skip trailing junk */
    pm_close(pkfile);
    for (car = 0; car < MAXPKCHAR; car++)
        if (filename[car])
            pm_message("Warning: No character in position %d (file %s).",
                       car, filename[car]) ;
    pm_message("%d bytes read from packed file.", pktopbm_pkloc-1) ;
    return 0;
}
