/* free2pbm :
 *  "DB-Z" & "ZAURUS" free hand memo, "PV-F1" action board(figure) 
 *  "Wiz" quick memo 
 *  --> Plain/Raw PBM
 *
 * (C) Copyright 1994,1995 by M.Sato
 *
 * compile:
 *   cc -O  -o free2pbm free2pbm.c  # -O : optimize flag
 *
 * usage:
 *   free2pbm [options]
 *
 *   option:
 *     -n name : data name
 *
 * I/O files:
 *   stdin  : free hand memo data.
 *   stdout : plain PBM.
 *   stderr : messages.
 *
 */

#include <stdio.h>
#include <stdlib.h>   /* exit() */

static char verstr[]="@(#) free2pbm Ver.1.00  1995/10/31";

/********* type *******************/
typedef unsigned int  WORD;
typedef unsigned char BYTE;

/* free hand memo header */
typedef struct hdr {
    WORD length;    /* data length */
    WORD width;     /* data width - 1 */
    WORD height;    /* data height - 1 */
    BYTE qp;        /* dot size (QP value) */
    BYTE comprs;    /* compress type */
} HEADER;

/************ const **************/
#define FALSE 0
#define TRUE  1

/* value of HEADER.comprs */
#define LINE    1   /* compress type 1 (line compress) */
#define ALL     2   /* compress type 2 (compress all data) */

/* character */
#define D_QUOTE    '\"'
#define NEWLINE    '\n'
#define COMMA      ','

/* dot par byte */
#define D_BYTE  8

/* data/line(pbm output) */
#define FIELD_LINE 34

/* return value */
#define RD_OK      0
#define RD_ERR     1
#define WT_ERR     2
#define DATERR     3
#define BADARG     4
#define UNKNOWN    5

/* ending message */
static char *msg[] = {
    "convert ok",               /* for RD_OK  */
    "file read error",          /* for RD_ERR */
    "output error",             /* for WT_ERR */
    "data error",               /* for DATERR */
    "Usage: free2pbm [-v]",     /* for BADARG */
    "unknown compress type",    /* for UNKNOWN */
};

/* for automata */
#define CHR_NUM 3
/*
 * mode
 *  0 : COMMA as a field delimiter(default)
 *  1 : D_QUOTE(1st) readed
 *  2 : D_QUOTE(2nd)
 *  3 : data (non quoted)
 */
static char in_chrs[CHR_NUM] = {D_QUOTE, COMMA, 0};
static nextmode[][CHR_NUM] = { 
/* D_QUOTE, COMMA, other */
  { 1,      0,     3},
  { 2,      1,     1},
  { 2,      0,     1},
  { 3,      0,     3},
};

/************** utility functions ***********************************/
void prog_end(status)
  int status;
{
    fprintf(stderr, "%s\n", msg[status]);
    exit(status);
}

int get_chr()
{
    int c;
    if (EOF == (c =getchar())) prog_end(RD_ERR);
    return(c);
}

int chrtype(c)
  int c;
{
    int i;
    for (i = 0; in_chrs[i]; i++) {
      if (c == in_chrs[i]) return i;
    }
    return i;
}

int field(n)
  int n;
{
    int mode = 0;
    int count = 1;
    int c;
    do {
      c = get_chr();
    } while (c == NEWLINE);
    do {
      mode = nextmode[mode][chrtype(c)];
      if (mode == 0) {
	count++;
	if (count == n) break;
      }
      c = get_chr();
    } while (c != NEWLINE);
    return count;
}

void skip_DQ()
{
    int c;
    if ((c = get_chr()) != D_QUOTE) ungetc(stdin,c);
}


/************* functions for tegaki-memo deta read ******************/
BYTE ctox(c)
  int c;
{

    switch (c) {
    case '0': return 0x0;
    case '1': return 0x1;
    case '2': return 0x2;
    case '3': return 0x3;
    case '4': return 0x4;
    case '5': return 0x5;
    case '6': return 0x6;
    case '7': return 0x7;
    case '8': return 0x8;
    case '9': return 0x9;
    case 'A': return 0xa;
    case 'B': return 0xb;
    case 'C': return 0xc;
    case 'D': return 0xd;
    case 'E': return 0xe;
    case 'F': return 0xf;
    default:
      prog_end(DATERR);
    }
}

BYTE readb()
{
    BYTE c1, c2;

    c1 = ctox(get_chr());
    c2 = ctox(get_chr());

    return c1*0x10 + c2;
}

WORD readw()
{
    WORD val_lo, val_hi;
/*
 *  ORDER of 'val_lo' and 'val_hi' is VERY important!!
 */
    val_lo = readb();
    val_hi = readb();
    return val_hi*0x100 + val_lo;
}

/************* functions for PBM output  ******************/
void pbm_start(width, height)
  unsigned int width, height;
{
    printf("P1\n%d %d\n", width, height);
}


void pbm_out(pattern, widthp)
  BYTE pattern;
  WORD *widthp;
{
    static int count = 0;
    BYTE mask=0x80;
    int i;

    do {
      if (*widthp > 0) {
        putchar((mask & pattern) ? '1' : '0');
        putchar(' ');
	--(*widthp);
        if ((++count % FIELD_LINE) == 0) {
          putchar('\n'); /* insert newline */
          count = 0;
        }
      }
      mask>>=1;
    } while (mask);
}

void pbm_end()
{
    fflush(stdout);
}

/************* data convertion main *********************************/
int convert()
{
    HEADER header;
    WORD x_len;
    WORD x_count;
    BYTE repeat;
    BYTE pattern;
    WORD width;

    /* read header */
    header.length = readw(); /* not used in this program */
    header.width  = readw();
    header.height = readw();
    header.qp     = readb(); /* not used in this program */
    header.comprs = readb();

    /* output header */
    pbm_start(++header.width, ++header.height);


    /********** main loop **********/
    switch (header.comprs) {
    case LINE:
      /* loop by data height */
      for (; header.height != 0; --header.height) {

        if ((x_count = readw()) == 0) return(DATERR);
	width = header.width;

        while (x_count != 0) {
          repeat = readb();
          x_count--;

          if ((repeat & 0x80) == 0) {       /* not compressed */
            x_count -= (++repeat);            /* repeat+1 datas */
            while (repeat-- > 0)
              pbm_out(readb(),&width);
          } else {                          /* compressed */
            pattern = readb();
            repeat = 0x101 - repeat;        /* 0x101-repeat datas */
            x_count--;
            while (repeat-- > 0) pbm_out(pattern,&width);
          }
        }
      }
      break;

    case ALL: 

      width = header.width;
      x_len = (header.width + D_BYTE-1) / D_BYTE;
      x_count = x_len;

      while (header.height != 0) {
        repeat = readb();
        if ((repeat & 0x80) == 0) {       /* not compressed */
          do {
            pbm_out(readb(), &width);
            if (--x_count == 0) {
              header.height--;
              x_count = x_len;
            }
          } while (repeat-- > 0);
	  if (width == 0) width = header.width; 
        } else {                         /* compressed */
          pattern = readb();
          for (repeat = 0x101-repeat; repeat > 0; repeat--) {
            pbm_out(pattern, &width);
            if (--x_count == 0) {
              header.height--;
              x_count = x_len;
            }
          }
	  if (width == 0) width = header.width; 
        }
      }
      break;
    default:
      prog_end(UNKNOWN);
    }
    /* ignore data end codes */

    pbm_end();
    return(RD_OK);
}

void main(argc, argv)
  int argc;
  char *argv[];
{
    int i;

    for (i = 1; i < argc; i++) {
      if (argv[i][0] == '-') {
        switch (argv[i][1]) {
        case 'v':
          fprintf(stderr,"%s\n", verstr);
          break;
        default:
          prog_end(BADARG);
        }
      } else {
        prog_end(BADARG);
      }
    }

    /* main */

    switch (i = field(0)) {
    case 4:  /* "I","SCRT","CLAS","IMG1" : DB-Z, ZAURUS */
             /* "I","SCRT","INDY","IMG1" : Wiz */
    case 3:  /* "I","SCRT","IMG1"        : PV-F1 */
      field(0); /* next line */
      field(i);
      skip_DQ();
      prog_end(convert());
    default:
      prog_end(UNKNOWN);
    }
}
