/* t1binary
 *
 * This program takes an Adobe Type-1 font program in ASCII (PFA) format and
 * converts it to binary (PFB) format.
 *
 * Copyright (c) 1992 by I. Lee Hetherington, all rights reserved.
 *
 * Permission is hereby granted to use, modify, and distribute this program
 * for any purpose provided this copyright notice and the one below remain
 * intact. 
 *
 * I. Lee Hetherington (ilh@lcs.mit.edu)
 *
 * $Log:	t1binary.c,v $
 * Revision 1.1  92/05/22  11:58:17  ilh
 * initial version
 * 
 */

#ifndef lint
static char rcsid[] =
  "@(#) $Id: t1binary.c,v 1.1 92/05/22 11:58:17 ilh Exp $";
static char copyright[] =
  "@(#) Copyright (c) 1992 by I. Lee Hetherington, all rights reserved.";
#endif

/* Note: this is ANSI C. */

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

#define BANNER   "This is t1binary 1.1.\n"
#define LINESIZE 256

#define MAXBLOCKLEN ((1<<17)-6)
#define MINBLOCKLEN ((1<<8)-6)

#define MARKER   128
#define ASCII    1
#define BINARY   2
#define DONE     3

typedef unsigned char byte;

static FILE *ifp = stdin;
static FILE *ofp = stdout;
static char line[LINESIZE];

/* for PFB block buffering */
static byte blockbuf[MAXBLOCKLEN];
static int blocklen = MAXBLOCKLEN;
static int blockpos = -1;
static int blocktyp = ASCII;

/* This function flushes a buffered PFB block. */

static void output_block()
{
  int i;

  /* output four-byte block length */
  fputc(blockpos & 0xff, ofp);
  fputc((blockpos >> 8) & 0xff, ofp);
  fputc((blockpos >> 16) & 0xff, ofp);
  fputc((blockpos >> 24) & 0xff, ofp);

  /* output block data */
  for (i = 0; i < blockpos; i++)
    fputc(blockbuf[i], ofp);

  /* mark block buffer empty and uninitialized */
  blockpos =  -1;
}

/* This function outputs a single byte.  If output is in PFB format then output
   is buffered through blockbuf[].  If output is in PFA format, then output
   will be hexadecimal if in_eexec is set, ASCII otherwise. */

static void output_byte(byte b)
{
  if (blockpos < 0) {
    fputc(MARKER, ofp);
    fputc(blocktyp, ofp);
    blockpos = 0;
  }
  blockbuf[blockpos++] = b;
  if (blockpos == blocklen)
    output_block();
}

/* This function outputs a null-terminated string through the PFB buffering. */

static void output_string(char *string)
{
  while (*string)
    output_byte((byte) *string++);
}

/* This function returns the value (0-15) of a single hex digit.  It returns
   0 for an invalid hex digit. */

static int hexval(char c)
{
  if (c >= 'A' && c <= 'F')
    return c - 'A' + 10;
  else if (c >= 'a' && c <= 'f')
    return c - 'a' + 10;
  else if (c >= '0' && c <= '9')
    return c - '0';
  else
    return 0;
}

/* This function outputs the binary data associated with a string of
   hexadecimal digits.  There must be an even number of digits. */

static void output_hex_string(char *string)
{
  while (string[0] && string[0] != '\n') {
    if (!string[1]) {
      fprintf(stderr, "error: only one hex digit\n");
      exit(1);
    }
    output_byte((byte)((hexval(string[0]) << 4) + hexval(string[1])));
    string += 2;
  }
}

/* This function returns 1 if the string contains all '0's. */

static int all_zeroes(char *string)
{
  while (*string == '0')
    string++;
  return *string == '\0' || *string == '\n';
}

static void usage()
{
  fprintf(stderr,
	  "usage: t1binary [-l block-length] [input [output]]\n");
  fprintf(stderr,
	  "The block length applies to the length of blocks in the\n");
  fprintf(stderr,
	  "PFB output file; the default is to use the largest possible.\n");
  exit(1);
}

static void print_banner()
{
  static char rcs_revision[] = "$Revision: 1.1 $";
  static char revision[20];

  if (sscanf(rcs_revision, "$Revision: %19s", revision) != 1)
    revision[0] = '\0';
  fprintf(stderr, "This is t1binary %s.\n", revision);
}

int main(int argc, char **argv)
{
  int c;

  extern char *optarg;
  extern int optind;
  extern int getopt(int argc, char **argv, char *optstring);

  print_banner();

  /* interpret command line arguments using getopt */
  while ((c = getopt(argc, argv, "l:")) != -1)
    switch (c) {
    case 'l':
      blocklen = atoi(optarg);
      if (blocklen < MINBLOCKLEN) {
	blocklen = MINBLOCKLEN;
	fprintf(stderr,
		"warning: using minimum block length of %d\n",
		blocklen);
      } else if (blocklen > MAXBLOCKLEN) {
	blocklen = MAXBLOCKLEN;
	fprintf(stderr,
		"warning: using maximum block length of %d\n",
		blocklen);
      }
      break;
    default:
      usage();
      break;
    }
  if (argc - optind > 2)
    usage();

  /* possibly open input & output files */
  if (argc - optind >= 1) {
    ifp = fopen(argv[optind], "r");
    if (!ifp) {
      fprintf(stderr, "error: cannot open %s for reading\n",
	      argv[optind]);
      exit(1);
    }
  }
  if (argc - optind >= 2) {
    ofp = fopen(argv[optind + 1], "w");
    if (!ofp) {
      fprintf(stderr, "error: cannot open %s for writing\n",
	      argv[optind + 1]);
      exit(1);
    }
  }

  /* peek at first byte to see if it is the PFB marker 0x80 */
  c = fgetc(ifp);
  if (c == MARKER) {
    fprintf(stderr,
	    "error: input may already be binary (starts with 0x80)\n");
    exit(1);
  }
  ungetc(c, ifp);

  /* Finally, we loop until no more input.  We need to look for `currentfile
     eexec' to start eexec section (hex to binary conversion) and line of all
     zeros to switch back to ASCII. */

  while (fgets(line, LINESIZE, ifp), !feof(ifp) && !ferror(ifp)) {
    if (blocktyp == ASCII && strcmp(line, "currentfile eexec\n") == 0) {
      output_string(line);
      output_block();
      blocktyp = BINARY;
    } else if (blocktyp == BINARY && all_zeroes(line)) {
      output_block();
      blocktyp = ASCII;
      output_string(line);
    } else if (blocktyp == ASCII) {
      output_string(line);
    } else {
      output_hex_string(line);
    }
  }
  output_block();
  fputc(MARKER, ofp);
  fputc(DONE, ofp);

  fclose(ifp);
  fclose(ofp);

  return 0;
}
