/*
  pbmtopk, adapted from "pxtopk.c by tomas rokicki" by AJCD 1/8/90

  References (retrieved May 31 2015):
  Packed (PK) Font File Format
  https://www.tug.org/TUGboat/tb06-3/tb13pk.pdf

  Tex Font Metric Files (TFM)
  https://www.tug.org/TUGboat/tb06-1/tb11gf.pdf
*/

#define _DEFAULT_SOURCE 1  /* New name for SVID & BSD source defines */
#define _BSD_SOURCE 1      /* Make sure strdup() is in string.h */
#define _XOPEN_SOURCE 500  /* Make sure strdup() is in string.h */

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

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

#define MAXPKCHAR 256
#define MAXOPTLINE 200
#define MAXWIDTHTAB 256
#define MAXHEIGHTTAB 16
#define MAXDEPTHTAB 16
#define MAXITALICTAB 64
#define MAXPARAMS 30
#define NAMELENGTH 80

/*-----------------------------------------------------------------------
Macros to handle fixed point numbers

This program uses uses fixed-point numbers to store data where
normally a floating-point data type (float or double) would be
employed.

Numbers that contain fractions are stored as signed integers.
The 20 least-significant bits are for the fractional part, the rest
(12 bits assuming that int is 32 bit) are for the integer part.
The technical term for this is "Q20" or "Q12.20" notation.

Float/double data is converted to Q20 fixed point by multiplying
by 2^20 (= 1048576).  The opposite conversion is conducted by
dividing by 2^20.

The Q20 data must be within the range -16 < r < 16.  The reason
behind this restriction is unclear.  The program generally writes
Q20 data to the output files in 32 bits.  (Exception: in function
shipchar() there is a provision to write Q20 data in 24 bits,
provided that 24 bits is sufficient.)
---------------------------------------------------------------------*/

#define fixword(d) ((int)((double)(d)*1048576))
#define unfixword(f) ((double)(f) / 1048576)
#define fixrange(f) ((f) < 16777216 && (f) > -16777216)
#define designunits(p) ((p)*72.27/(double)resolution/unfixword(designsize))

/* character flags: in order of appearance in option files. */
#define XOFFSET     1
#define YOFFSET     2
#define HORZESC     4
#define VERTESC     8
#define TFMWIDTH   16
#define TFMHEIGHT  32
#define TFMDEPTH   64
#define TFMITALIC 128

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

static integer resolution, designsize ;
static char *filename[MAXPKCHAR] ;

static integer xoffset[MAXPKCHAR] ;
static integer yoffset[MAXPKCHAR] ;
static integer horzesc[MAXPKCHAR] ;
static integer vertesc[MAXPKCHAR] ;

static byte tfmindex[MAXPKCHAR] ;
static byte hgtindex[MAXPKCHAR] ;
static byte depindex[MAXPKCHAR] ;
static byte italindex[MAXPKCHAR] ;
static byte charflags[MAXPKCHAR] ;

static bit **bitmap ;
static integer smallestch = MAXPKCHAR ;
static integer largestch = -1;
static integer emwidth  = 0;
static integer checksum ;
static const char *codingscheme = "GRAPHIC" ;
static const char *familyname = "PBM" ;

static integer widthtab[MAXWIDTHTAB] = {0}; /* TFM widths */
static integer numwidth = 1;      /* number of entries in width table */
static integer heighttab[MAXHEIGHTTAB]  = {0};
static integer numheight = 1;
static integer depthtab[MAXDEPTHTAB] = {0};
static integer numdepth = 1;
static integer italictab[MAXITALICTAB] = {0};
static integer numitalic = 1;
static integer parameters[MAXPARAMS] = {0};
static integer numparam = 0;

static ASCIIcode xord[128] ;
static char xchr[256] = {
    '?', '?', '?', '?', '?', '?', '?', '?',
    '?', '?', '?', '?', '?', '?', '?', '?',
    '?', '?', '?', '?', '?', '?', '?', '?',
    '?', '?', '?', '?', '?', '?', '?', '?',
    ' ', '!', '"', '#', '$', '%', '&', '\'',
    '(', ')', '*', '+', ',', '-', '.', '/',
    '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', '{', '|', '}', '~', '?' };

static FILE *tfmfile, *pkfile ;
static char tfmname[NAMELENGTH+1], pkname[NAMELENGTH+1] ;
static integer pbmtopk_pkloc = 0 ;
static integer bitweight ;
static integer outputbyte ;
static integer car ;
static integer hppp ;
static integer width ;
static integer height ;

/* check sum algorithm (in Pascal):

compute_checksum()
    begin
        c0:=bc; c1:=ec; c2:=bc; c3:=ec;
        for c:=bc to ec do if char_wd[c]>0 then begin
            temp_width:=memory[char_wd[c]];
            if design_units<>unity then
               temp_width:=round((temp_width/design_units)*1048576.0);
            temp_width:=temp_width + (c+4)*@'20000000;
                {this should be positive}
            c0:=(c0+c0+temp_width) mod 255;
            c1:=(c1+c1+temp_width) mod 253;
            c2:=(c2+c2+temp_width) mod 251;
            c3:=(c3+c3+temp_width) mod 247;
            end;
        header_bytes[check_sum_loc]:=c0;
        header_bytes[check_sum_loc+1]:=c1;
        header_bytes[check_sum_loc+2]:=c2;
        header_bytes[check_sum_loc+3]:=c3;
        end
*/

#define add_tfmwidth(v) (add_tfmtable(widthtab, &numwidth, v, MAXWIDTHTAB,\
                                      "TFM width"))
#define add_tfmheight(v) (add_tfmtable(heighttab, &numheight, v, MAXHEIGHTTAB,\
                                       "TFM height"))
#define add_tfmdepth(v) (add_tfmtable(depthtab, &numdepth, v, MAXDEPTHTAB,\
                                      "TFM depth"))
#define add_tfmitalic(v) (add_tfmtable(italictab, &numitalic, v, MAXITALICTAB,\
                                       "Italic correction"))


static byte
add_tfmtable(int *        const table,
             int *        const count,
             int          const value,
             int          const max_count,
             const char * const name) {

    integer i;
    for (i = 0; i < *count; i++) /* search for value in tfm table */
        if (table[i] == value) return (byte)i;
    if (*count >= max_count)
        pm_error("too many values in %s table", name) ;
    if (!fixrange(value))
        pm_error("%s %f for char %d out of range",
                 name, unfixword(value), car);
    table[*count] = value ;
    return (*count)++ ;
}



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

    char *slash = strrchr(name, '/');
    char *dot = strrchr(name, '.');

    if ((dot && slash ? dot < slash : !dot) && !streq(name, "-"))
        strcat(name, suffix);
}



/* initialize the PK parameters */
static void
initialize_pk(void) {
    integer i ;
    pm_message("This is PBMtoPK, version 2.4") ;
    for (i = 127 ; i <= 255 ; i ++) xchr[i] = '?' ;
    for (i = 0 ; i <= 127 ; i ++) xord[i] = 32 ;
    for (i = 32 ; i < 127 ; i ++) xord[(int)xchr[i]] = i ;
    for (i = 0; i < MAXPKCHAR; i++) {
        filename[i] = NULL;
        charflags[i] = 0;
    }
    designsize = fixword(1.0) ;
}



/* write a single byte to the PK file */
static void
pbmtopk_pkbyte(integer const b_in) {
    integer b;

    b = b_in;  /* initial value */

    if (b < 0)
        b += 256 ;
    putc(b, pkfile) ;
    pbmtopk_pkloc++ ;
}



/* write two bytes to the PK file */
static void
pkhalfword(integer const a_in) {
    integer a;

    a = a_in;

    if (a < 0)
        a += 65536 ;
    pbmtopk_pkbyte(a >> 8) ;
    pbmtopk_pkbyte(a & 255) ;
}



/* write three bytes to the PK file */
static void
pkthreebytes(integer const a) {

    pbmtopk_pkbyte((a>>16) & 255) ;
    pbmtopk_pkbyte((a>>8) & 255) ;
    pbmtopk_pkbyte(a & 255) ;
}



/* write four bytes to the PK file */
static void
pkword(integer const a) {
    pbmtopk_pkbyte((a>>24) & 255) ;
    pbmtopk_pkbyte((a>>16) & 255) ;
    pbmtopk_pkbyte((a>>8) & 255) ;
    pbmtopk_pkbyte(a & 255) ;
}



/* write a nibble to the PK file */
static void
pknyb(integer const a) {

    if (bitweight == 16) {
        outputbyte = (a<<4) ;
        bitweight = 1 ;
    } else {
        pbmtopk_pkbyte(outputbyte + a) ;
        bitweight = 16 ;
    }
}



/* write preamble to PK file */
static void
writepreamble(void) {
    integer i ;
    const char * const comment = "PBMtoPK 2.4 output" ;

    pbmtopk_pkbyte(247) ;                /* PRE command */
    pbmtopk_pkbyte(89) ;             /* PK file type */
    pbmtopk_pkbyte(strlen(comment)) ;            /* output comment */
    for (i = 0 ; i < strlen(comment); i++)
        pbmtopk_pkbyte(xord[(int)comment[i]]) ;
    pkword(designsize) ;             /* write designsize */
    pkword(checksum) ;       /* write checksum; calculate if possible */
    pkword(hppp) ;               /* write H pixels per point */
    pkword(hppp) ;               /* write V pixels per point */
}



/* write postamble to PK file, padded to word length */
static void
writepostamble(void) {
    pbmtopk_pkbyte(245) ;                /* POST command */
    while (pbmtopk_pkloc % 4)
        pbmtopk_pkbyte(246) ;             /* pad with no-ops */
    pm_message("%d bytes written to packed file.", pbmtopk_pkloc) ;
}



/* write a byte to the TFM file */
static void
tfmbyte(integer const b_in) {
    integer b;

    b = b_in;

    if (b < 0) b += 256 ;
    putc(b, tfmfile) ;
}



/* write a half word to the TFM file */
static void
tfmhalfword(integer const a_in) {

    integer a;

    a = a_in;

    if (a < 0) a += 65536 ;
    tfmbyte(a >> 8) ;
    tfmbyte(a & 255) ;
}



/* write a word to the TFM file */
static void
tfmword(integer const a) {
    tfmbyte((a>>24) & 255) ;
    tfmbyte((a>>16) & 255) ;
    tfmbyte((a>>8) & 255) ;
    tfmbyte(a & 255) ;
}



/* write the whole TFM file for the font */
static void
writetfmfile(void) {
    integer totallength ;
    integer headersize = 17;
    integer i ;

    if (largestch - smallestch < 0) {
        largestch = 0;
        smallestch = 1;
    }
    if (numparam < 7) /* set default parameters */
        switch (numparam) {
        case 0: /* slant */
            parameters[numparam++] = 0 ;
        case 1: /* space */
            parameters[numparam++] = fixword(designunits(emwidth/3.0));
        case 2: /* space_stretch */
            parameters[numparam++] = fixword(unfixword(parameters[1])/2.0) ;
        case 3: /* space_shrink */
            parameters[numparam++] = fixword(unfixword(parameters[1])/3.0) ;
        case 4: /* x_height */
            parameters[numparam++] = fixword(0.45);
        case 5: /* quad */
            parameters[numparam++] = fixword(designunits(emwidth)) ;
        case 6: /* extra_space */
            parameters[numparam++] = fixword(unfixword(parameters[1])/3.0) ;
        }
    totallength = 6 + headersize + (largestch+1-smallestch) +
        numwidth + numheight + numdepth + numitalic + numparam ;
    /* lengths */
    tfmhalfword(totallength) ;           /* write file TFM length */
    tfmhalfword(headersize) ;            /* write TFM header length */
    tfmhalfword(smallestch) ;            /* write lowest char index */
    tfmhalfword(largestch) ;         /* write highest char index */
    tfmhalfword(numwidth) ;          /* write number of widths */
    tfmhalfword(numheight) ;         /* write number of heights */
    tfmhalfword(numdepth) ;          /* write number of depths */
    tfmhalfword(numitalic) ;         /* write number of italcorrs */
    tfmhalfword(0) ;             /* lig/kern table */
    tfmhalfword(0) ;             /* kern table */
    tfmhalfword(0) ;             /* extensible char table */
    tfmhalfword(numparam) ;          /* number of fontdimens */
    /* header */
    tfmword(checksum) ;              /* write checksum */
    tfmword(designsize) ;            /* write designsize */
    if (strlen(codingscheme) > 39)
        tfmbyte(39) ; /* write coding scheme len */
    else
        tfmbyte(strlen(codingscheme)) ;
    for (i = 0; i < 39; i++)         /* write coding scheme */
        if
            (*codingscheme) tfmbyte(xord[(int)(*codingscheme++)]) ;
        else
            tfmbyte(0) ;
    if (strlen(familyname) > 19)
        tfmbyte(19) ;   /* write family length */
    else
        tfmbyte(strlen(familyname)) ;
    for (i = 0; i < 19; i++)         /* write family */
        if (*familyname)
            tfmbyte(xord[(int)(*familyname++)]) ;
        else
            tfmbyte(0) ;
    /* char_info */
    for (car = smallestch; car <= largestch; car++)
        if (filename[car]) {          /* write character info */
            tfmbyte(tfmindex[car]) ;
            tfmbyte((hgtindex[car]<<4) + depindex[car]) ;
            tfmbyte(italindex[car]<<2) ;
            tfmbyte(0) ;
        } else
            tfmword(0) ;
    /* width table */
    for (i = 0; i < numwidth; i++) tfmword(widthtab[i]) ;
    /* height table */
    for (i = 0; i < numheight; i++) tfmword(heighttab[i]) ;
    /* depth table */
    for (i = 0; i < numdepth; i++) tfmword(depthtab[i]) ;
    /* italic correction table */
    for (i = 0; i < numitalic; i++) tfmword(italictab[i]) ;
    /* no lig_kern, kern, or extent tables */
    /* fontdimen table */
    for (i = 0; i < numparam; i++)
        if (i && !fixrange(parameters[i]))
            pm_error("parameter %d out of range (-p)", i);
        else
            tfmword(parameters[i]) ;
    pm_message("%d bytes written to tfm file.", totallength*4) ;
}



/* read a character from a PBM file */
static void readcharacter(void) {
    FILE *fp;

    fp = pm_openr(filename[car]);
    bitmap = pbm_readpbm(fp, &width, &height) ;
    pm_close(fp) ;

    if ((charflags[car] & HORZESC) == 0) horzesc[car] = width ;
    if ((charflags[car] & VERTESC) == 0) vertesc[car] = 0;
    if ((charflags[car] & XOFFSET) == 0) xoffset[car] = 0;
    if ((charflags[car] & YOFFSET) == 0) yoffset[car] = height-1;
    if ((charflags[car] & TFMWIDTH) == 0)
        tfmindex[car] = add_tfmwidth(fixword(designunits(width)));
    if ((charflags[car] & TFMHEIGHT) == 0)
        hgtindex[car] = add_tfmheight(fixword(designunits(yoffset[car]+1)));
    if ((charflags[car] & TFMDEPTH) == 0)
        depindex[car] =
            add_tfmdepth(fixword(designunits(height-1-yoffset[car])));
    if ((charflags[car] & TFMITALIC) == 0) italindex[car] = 0;

    if (car < smallestch) smallestch = car;
    if (car > largestch) largestch = car;
    if (width > emwidth) emwidth = width ;
}



/* test if two rows of the PBM are the same */
static int
equal(const bit * const row1,
      const bit * const row2) {

    integer i ;

    for (i = 0; i < width; i++)
        if (row1[i] != row2[i])
            return (0) ;

    return(1) ;
}



static void
shipcharacter(void) {

    integer compsize ;
    integer i, j, k ;
    bit *zerorow, *onesrow ;
    integer *repeatptr, *bitcounts ;
    integer count ;
    integer test ;
    integer curptr, rowptr ;
    integer bitval ;
    integer repeatflag ;
    integer colptr ;
    integer currepeat ;
    integer dynf ;
    integer deriv[14] ;
    integer bcompsize ;
    boolean firston ;
    integer flagbyte ;
    boolean state ;
    boolean on ;
    integer hbit ;
    integer pbit ;
    boolean ron, son ;
    integer rcount, scount ;
    integer ri, si ;
    integer max2 ;
    integer predpkloc ;
    integer buff ;

    integer tfwid = widthtab[tfmindex[car]] ;
    integer hesc = horzesc[car] ;
    integer vesc = vertesc[car] ;
    integer xoff = xoffset[car] ;
    integer yoff = yoffset[car] ;

    MALLOCARRAY(repeatptr, height + 1);
    MALLOCARRAY(bitcounts, height * width);
    if (repeatptr == NULL || bitcounts == NULL)
        pm_error("out of memory while allocating bit counts");
    zerorow = pbm_allocrow(width) ;      /* initialize plain rows */
    onesrow = pbm_allocrow(width) ;
    for (i = 0 ; i < width ; i++) {
        zerorow[i] = PBM_WHITE ;
        onesrow[i] = PBM_BLACK ;
    }
    for (i=0; i < height; i = k) {       /* set repeat pointers */
        k = i + 1;
        if (!equal(bitmap[i], zerorow) && !equal(bitmap[i], onesrow)) {
            while (k < height && equal(bitmap[i], bitmap[k]))
                k++;
            repeatptr[i] = k - i - 1;
        } else {
            repeatptr[i] = 0;
        }
    }
    repeatptr[height] = 0 ;
    colptr = width - 1 ;
    repeatflag = currepeat = curptr = count = rowptr = 0 ;
    test = PBM_WHITE ;
    do {
        colptr++ ;
        if (colptr == width) {            /* end of row, get next row */
            colptr = 0 ;
            rowptr = currepeat ;
            if (repeatptr[currepeat] > 0) {
                repeatflag = repeatptr[currepeat] ;
                currepeat += repeatflag ;
                rowptr += repeatflag ;
            }
            currepeat++ ;
        }
        if (rowptr >= height) bitval = -1 ;
        else bitval = bitmap[rowptr][colptr] ;
        if (bitval == test) count++ ;     /* count repeated pixels */
        else {                    /* end of pixel run */
            bitcounts[curptr++] = count ;
            if (curptr+3 >= height*width)
                pm_error("out of memory while saving character counts");
            count = 1 ;
            test = bitval ;
            if (repeatflag > 0) {
                bitcounts[curptr++] = -repeatflag ;
                repeatflag = 0 ;
            }
        }
    } while (test != -1) ;
    bitcounts[curptr] = 0 ;
    bitcounts[curptr + 1] = 0 ;
    for (i = 1 ; i <= 13 ; i ++) deriv[i] = 0 ;
    i = firston = (bitcounts[0] == 0) ;
    compsize = 0 ;
    while (bitcounts[i] != 0) {          /* calculate dyn_f */
        j = bitcounts[i] ;
        if (j == -1) compsize++ ;
        else {
            if (j < 0) {
                compsize++ ;
                j = -j ;
            }
            if (j < 209) compsize += 2 ;
            else {
                k = j - 193 ;
                while (k >= 16) {
                    k >>= 4 ;
                    compsize += 2 ;
                }
                compsize++ ;
            }
            if (j < 14) (deriv[j])-- ;
            else if (j < 209) (deriv[(223 - j) / 15])++ ;
            else {
                k = 16 ;
                while (((k<<4) < j + 3)) k <<= 4 ;
                if (j - k <= 192)
                    deriv[(207 - j + k) / 15] += 2 ;
            }
        }
        i++ ;
    }
    bcompsize = compsize ;
    dynf = 0 ;
    for (i = 1 ; i <= 13 ; i ++) {
        compsize += deriv[i] ;
        if (compsize <= bcompsize) {
            bcompsize = compsize ;
            dynf = i ;
        }
    }
    compsize = ((bcompsize + 1)>>1) ;
    if ((compsize > ((height*width+7)>>3)) || (height*width == 0)) {
        compsize = ((height*width+7)>>3) ;
        dynf = 14 ;
    }
    flagbyte = (dynf<<4) ;
    if (firston) flagbyte |= 8 ;
    if (tfwid > 16777215 || tfwid < 0 || hesc < 0 || vesc != 0 ||
        compsize > 196579 || width > 65535 || height > 65535 ||
        xoff > 32767 || yoff > 32767 || xoff < -32768 || yoff < -32768) {
        flagbyte |= 7 ;               /* long form preamble */
        pbmtopk_pkbyte(flagbyte) ;
        compsize += 28 ;
        pkword(compsize) ;            /* char packet size */
        pkword(car) ;             /* character number */
        predpkloc = pbmtopk_pkloc + compsize ;
        pkword(tfwid) ;               /* TFM width */
        pkword(hesc<<16) ;            /* horiz escapement */
        pkword(vesc<<16) ;            /* vert escapement */
        pkword(width) ;               /* bounding box width */
        pkword(height) ;              /* bounding box height */
        pkword(xoff) ;                /* horiz offset */
        pkword(yoff) ;                /* vert offset */
    } else if (hesc > 255 || width > 255 || height > 255 ||
               xoff > 127 || yoff > 127 || xoff < -128 ||
               yoff < -128 || compsize > 1016) {
        compsize += 13 ;              /* extended short preamble */
        flagbyte += (compsize>>16) + 4 ;
        pbmtopk_pkbyte(flagbyte) ;
        pkhalfword(compsize & 65535) ;        /* char packet size */
        pbmtopk_pkbyte(car) ;             /* character number */
        predpkloc = pbmtopk_pkloc + compsize ;
        pkthreebytes(tfwid) ;         /* TFM width */
        pkhalfword(hesc) ;            /* horiz escapement */
        pkhalfword(width) ;           /* bounding box width */
        pkhalfword(height) ;          /* bounding box height */
        pkhalfword(xoff) ;            /* horiz offset */
        pkhalfword(yoff) ;            /* vert offset */
    } else {
        compsize += 8 ;               /* short form preamble */
        flagbyte = flagbyte + (compsize>>8) ;
        pbmtopk_pkbyte(flagbyte) ;
        pbmtopk_pkbyte(compsize & 255) ;          /* char packet size */
        pbmtopk_pkbyte(car) ;             /* character number */
        predpkloc = pbmtopk_pkloc + compsize ;
        pkthreebytes(tfwid) ;         /* TFM width */
        pbmtopk_pkbyte(hesc) ;                /* horiz escapement */
        pbmtopk_pkbyte(width) ;               /* bounding box width */
        pbmtopk_pkbyte(height) ;              /* bounding box height */
        pbmtopk_pkbyte(xoff) ;                /* horiz offset */
        pbmtopk_pkbyte(yoff) ;                /* vert offset */
    }
    if (dynf != 14) {                /* write packed character */
        bitweight = 16 ;
        max2 = 208 - 15 * dynf ;
        i = firston ;
        while (bitcounts[i] != 0) {
            j = bitcounts[i] ;
            if (j == - 1) pknyb(15) ;
            else {
                if (j < 0) {
                    pknyb(14) ;
                    j = -j ;
                }
                if (j <= dynf) pknyb(j) ;
                else if (j <= max2) {
                    j -= dynf + 1 ;
                    pknyb((j >> 4) + dynf + 1) ;
                    pknyb((j & 15)) ;
                } else {
                    j -= max2 - 15 ;
                    k = 16 ;
                    while (k <= j) {
                        k <<= 4 ;
                        pknyb(0) ;
                    }
                    while (k > 1) {
                        k >>= 4 ;
                        pknyb(j / k) ;
                        j = j % k ;
                    }
                }
            }
            i++ ;
        }
        if (bitweight != 16) pbmtopk_pkbyte(outputbyte) ;
    } else {                 /* write bitmap character */
        buff = 0 ;
        pbit = 8 ;
        i = firston ;
        hbit = width ;
        on = ! firston ;
        state = 0 ;
        count = repeatflag = 0 ;
        while ((bitcounts[i] != 0) || state || (count > 0)) {
            if (state) {
                count = rcount ;
                i = ri ;
                on = ron ;
                repeatflag-- ;
            } else {
                rcount = count ;
                ri = i ;
                ron = on ;
            }
            do {
                if (count == 0) {
                    if (bitcounts[i] < 0) {
                        if (! state) repeatflag = -bitcounts[i] ;
                        i++ ;
                    }
                    count = bitcounts[i] ;
                    i++ ;
                    on = !on ;
                }
                if ((count >= pbit) && (pbit < hbit)) {
                    if (on) buff += (1 << pbit) - 1 ;
                    pbmtopk_pkbyte(buff) ;
                    buff = 0 ;
                    hbit -= pbit ;
                    count -= pbit ;
                    pbit = 8 ;
                } else if ((count < pbit) && (count < hbit)) {
                    if (on) buff += (1 << pbit) - (1 << (pbit - count)) ;
                    pbit -=  count ;
                    hbit -= count ;
                    count = 0 ;
                } else {
                    if (on) buff += (1 << pbit) - (1 << (pbit - hbit)) ;
                    count -= hbit ;
                    pbit -= hbit ;
                    hbit = width ;
                    if (pbit == 0) {
                        pbmtopk_pkbyte(buff) ;
                        buff = 0 ;
                        pbit = 8 ;
                    }
                }
            } while (hbit != width) ;
            if (state && (repeatflag == 0)) {
                count = scount ;
                i = si ;
                on = son ;
                state = 0 ;
            } else if (! state && (repeatflag > 0)) {
                scount = count ;
                si = i ;
                son = on ;
                state = 1 ;
            }
        }
        if (pbit != 8) pbmtopk_pkbyte(buff) ;
    }
    if (predpkloc != pbmtopk_pkloc)
        pm_error("bad predicted character length: character %d", car);
    pbm_freerow(zerorow);
    pbm_freerow(onesrow);
    free((char *)repeatptr);
    free((char *)bitcounts);
}



/* check that character is in valid range */
static void
checkchar(void) {
    if (car < 0 || car >= MAXPKCHAR)
        pm_error("character must be in range 0 to %d", MAXPKCHAR-1) ;
}



/* read character information from an option file */
static void
optionfile(const char * const name) {

    FILE *fp ;
    char buffer[MAXOPTLINE] ;

    fp = pm_openr(name);
    while (!feof(fp)) {
        char *here = buffer;

        if (fgets(buffer, MAXOPTLINE, fp) == NULL) break ;
        while (ISSPACE(*here)) here++ ;
        if (*here && *here == '=') {
            if (sscanf(here+1, "%d", &car) != 1)
                pm_error("bad option file line %s", buffer) ;
        } else if (*here && *here != '%' && *here != '#') {
            char str[NAMELENGTH] ;
            integer i, n;

            checkchar() ;
            if (sscanf(here, "%s%n", str, &n) != 1)
                pm_error("bad option file line %s", buffer) ;
            filename[car] = strdup(str);
            if (filename[car] == NULL)
                pm_error("out of memory allocating filename %s", str);
            for (i = 1; i < 256; i<<=1) {
                here += n;
                if (sscanf(here, "%s%n", str, &n) != 1) break ;
                if (strcmp(str, "*")) {
                    charflags[car] |= i ;
                    switch (i) {
                    case XOFFSET:
                        xoffset[car] = atoi(str) ;
                        break ;
                    case YOFFSET:
                        yoffset[car] = atoi(str) ;
                        break ;
                    case HORZESC:
                        horzesc[car] = atoi(str) ;
                        break ;
                    case VERTESC:
                        vertesc[car] = atoi(str) ;
                        break ;
                    case TFMWIDTH:
                        tfmindex[car] = add_tfmwidth(fixword(atof(str))) ;
                        break ;
                    case TFMHEIGHT:
                        hgtindex[car] = add_tfmheight(fixword(atof(str))) ;
                        break ;
                    case TFMDEPTH:
                        depindex[car] = add_tfmdepth(fixword(atof(str))) ;
                        break ;
                    case TFMITALIC:
                        italindex[car] = add_tfmitalic(fixword(atof(str))) ;
                        break ;
                    }
                }
            }
            car++ ;
        }
    }
    pm_close(fp) ;
}



int
main(int argc, char *argv[]) {
    integer i, hesc, vesc, xoff, yoff, tfwid, tfdep, tfhgt, tfital ;
    byte flags ;
    const char * const usage = "pkfile[.pk] tfmfile[.tfm] dpi "
        "[-s designsize] [-p num param...]\n"
        "[-C codingscheme ] [-F family] [-c num | <char>]...\n"
        "<char> is:\n"
        "[-W tfmwidth] [-H tfmheight] [-D tfmdepth] [-I ital_corr] "
        "[-h horiz]\n"
        "[-v vert] [-x xoffset] [-y yoffset] file\n"
        "or:\n"
        "-f optfile\n" ;

    pbm_init(&argc, argv);
    initialize_pk() ;

    if (--argc < 1) pm_usage(usage) ;
    ++argv;
    if(strlen(*argv) + 4 > NAMELENGTH)
        pm_error("pkname is too long");
    strcpy(pkname, *argv) ;
    pbmtopk_add_suffix(pkname, ".pk") ;

    if (--argc < 1) pm_usage(usage);
    ++argv;
    if(strlen(*argv) + 4 > NAMELENGTH)
        pm_error("tfmname is too long");
    strcpy(tfmname, *argv) ;
    pbmtopk_add_suffix(tfmname, ".tfm") ;

    if (--argc < 1) pm_usage(usage) ;
    resolution = atoi(*++argv) ;
    if (resolution < 1 || resolution > 32767)
        pm_error("unlikely resolution %d dpi", resolution);

    car = flags = hesc = vesc = xoff = yoff = tfwid = 0;
    while (++argv, --argc) {
        if (argv[0][0] == '-' && argv[0][1]) {
            char c, *p;
            c = argv[0][1] ;
            if (argv[0][2]) p = *argv + 2 ;    /* set argument pointer */
            else if (++argv, --argc) p = *argv ;
            else pm_usage(usage) ;
            switch (c) {
            case 'C':
                codingscheme = p;
                break ;
            case 'F':
                familyname = p;
                break ;
            case 'c':
                car = atoi(p) ;
                break ;
            case 's':
                designsize = fixword(atof(p));
                if (designsize < 1048576)
                    pm_error("design size %f out of range",
                             unfixword(designsize));
            case 'h':
                hesc = atoi(p) ;
                flags |= HORZESC ;
                break ;
            case 'v':
                vesc = atoi(p) ;
                flags |= VERTESC ;
                break ;
            case 'x':
                xoff = atoi(p) ;
                flags |= XOFFSET ;
                break ;
            case 'y':
                yoff = atoi(p) ;
                flags |= YOFFSET ;
                break ;
            case 'W':
                tfwid = fixword(atof(p)) ;
                flags |= TFMWIDTH ;
                break ;
            case 'H':
                tfhgt = fixword(atof(p)) ;
                flags |= TFMHEIGHT ;
                break ;
            case 'D':
                tfdep = fixword(atof(p)) ;
                flags |= TFMDEPTH ;
                break ;
            case 'I':
                tfital = fixword(atof(p)) ;
                flags |= TFMITALIC ;
                break ;
            case 'f':
                optionfile(p) ;
                break ;
            case 'p':
                numparam = atoi(p);
                if (numparam < 1 || numparam > MAXPARAMS)
                    pm_error("parameter count %d out of range", numparam);
                for (i=0; i<numparam; i++)
                    if (++argv,--argc)
                        parameters[i] = fixword(atof(*argv)) ;
                    else
                        pm_error("not enough parameters (-p)");
                break ;
            default:
                pm_usage(usage) ;
            }
        } else  {
            checkchar() ;
            if (flags & TFMWIDTH)
                tfmindex[car] = add_tfmwidth(tfwid);
            if (flags & TFMDEPTH)
                depindex[car] = add_tfmdepth(tfdep);
            if (flags & TFMHEIGHT)
                hgtindex[car] = add_tfmheight(tfhgt);
            if (flags & TFMITALIC)
                italindex[car] = add_tfmitalic(tfital);
            horzesc[car] = hesc ;
            vertesc[car] = vesc ;
            xoffset[car] = xoff ;
            yoffset[car] = yoff ;
            filename[car] = *argv ;
            charflags[car] = flags ;
            car++ ;
            flags = 0;
        }
    }
    hppp = ROUND((resolution<<16) / 72.27) ;
    pkfile = pm_openw(pkname);
    tfmfile = pm_openw(tfmname);
    writepreamble() ;
    for (car = 0 ; car < MAXPKCHAR ; car++)
        if (filename[car]) {
            readcharacter() ;
            shipcharacter() ;
        }
    writepostamble() ;
    writetfmfile() ;
    pm_close(pkfile) ;
    pm_close(tfmfile) ;

    return 0;
}



