/*
 * fontfilt.c - cawf post-processing font filter
 *
 * V. Abell
 * Purdue University Computing Center
 *
 * Fontfilt filters output from cawf, the C version of Henry Spencer's
 * Amazingly Workable (text) Formatter, awf, to produce printer-specific
 * codes for bold and italic characters.  (Cawf provides modest support
 * for documents formatted with nroff's man(7) and ms(7) macros.)
 *
 * Fontfilt is based on work by and suggestions from Chet Creider
 * <creider@csd.uwo.ca>.
 */


/*
 *      Copyright (c) 1991 Purdue University Research Foundation,
 *      West Lafayette, Indiana 47907.  All rights reserved.
 *
 *      Written by Victor A. Abell <abe@mace.cc.purdue.edu>, Purdue
 *      University Computing Center.  Not derived from licensed software;
 *      derived from work by Chet Creider <creider@csd.uwo.ca>.
 *
 *      Permission is granted to anyone to use this software for any
 *      purpose on any computer system, and to alter it and redistribute
 *      it freely, subject to the following restrictions:
 *
 *      1. The author is not responsible for any consequences of use of
 *         this software, even if they arise from flaws in it.
 *
 *      2. The origin of this software must not be misrepresented, either
 *         by explicit claim or by omission.  Credits must appear in the
 *         documentation.
 *
 *      3. Altered versions must be plainly marked as such, and must not
 *         be misrepresented as being the original software.  Credits must
 *         appear in the documentation.
 *
 *      4. This notice may not be removed or altered.
 */


/*
 * Usage:
 *
 *      fontfilt [-c config] [-d device] [-f font] [file(s)]
 *
 *      where:
 *
 *              -c config       specifies an alternate configuration file
 *                              (default directory = CAWFLIB definition or
 *                                                   CAWFLIB env. variable)
 *
 *              -d device       specifies the output device
 *
 *              -f font         specifies the font to be used on the
 *                              output device
 *
 *              file(s)         the path(s) to the file(s) containing cawf,
 *                              -fe format output
 *
 * Cawf's font ESCape mode must be used - e. g.,
 *
 *      % cawf -fe -man cawf.1 | fontfilt -dlj3 -flg12
 */


/*
 * See fontfilt.cf for a list of supported devices and fonts, or use
 * the -h (help) option.  The default device is the last device named
 * in fontfilt.cf.
 */

#include <stdio.h>
#ifdef  STDLIB
#include <stdlib.h>
#endif
#ifdef  UNIX
#ifdef  USG
#include <string.h>
#else
#include <strings.h>
#endif
#else
#include <string.h>
#endif
#include <ctype.h>
#include <sys/types.h>
#include <sys/stat.h>

#include "cawflib.h"


/*
 * Local definitions
 */

#define BOLD            'B'
#define CONFIG          "fontfilt.cf"
#define ESC             0x1b
#define ITALIC          'I'
#define MAXLINE         512
#define ROMAN           'R'


/*
 * Local global variables
 */

static char *Besc = NULL;       /* bold font escape string pointer */
static char *Conf = NULL;       /* configuration file path */
static char *Defdev = NULL;     /* default device name (last name of
                                 * fontfilt.cf */
static char Font = ROMAN;       /* current font */
static char *Iesc = NULL;       /* italic font escape string pointer */
static char *Pname;             /* program name */
static char *Resc = NULL;       /* roman font escape string pointer */


/*
 * Structure for font definitions
 */

struct font {
        char *nm;               /* font name */
        char *fi;               /* font initialization character sequence */
        struct font *next;      /* next font fot this device */
};


/*
 * Device structure
 */

struct dev {
        char *nm;               /* device name */
        struct font *f;         /* supported fonts */
        char *b;                /* bold font ESCape sequence */
        char *i;                /* italic font ESCape sequence */
        char *r;                /* Roman font ESCape sequence */
        struct dev *next;       /* next device */
} *Dp = NULL;


/*
 * Externals
 */

extern char *optarg;                    /* getopt(3) argument pointer */
extern int optind;                      /* getopt(3) index */


/*
 * Function definitions
 */

static char *Convstr();
static int Convfont(), Readcf();
static void Bold(), Getec(), Italic(), Roman();

#ifndef STDLIB
char *getenv(), *malloc(), *strchr(), *strrchr();
#endif


/*
 * Main program
 */

main(argc, argv)
        int argc;                       /* argument count */
        char *argv[];                   /* argument pointers */
{
        register int c;                 /* character buffer */
        char *cnm = NULL;               /* config file name */
        char *dnm = NULL;               /* device name */
        struct dev *dp;                 /* device */
        char *fnm = NULL;               /* font name */
        int err = 0;                    /* argument error count */
        int fc, fx;                     /* file count and index */
        struct font *fp;                /* font pointer */
        FILE *fs;                       /* file stream */
        int help = 0;                   /* -h status */
        struct stat sbuf;               /* file stat() buffer */
        char *sep;                      /* separator pointer */
/*
 * Save program name.
 */
        if ((Pname = strrchr(argv[0], '\\')) != NULL)
                Pname++;
        else if ((Pname = strrchr(argv[0], '/')) != NULL)
                Pname++;
        else
                Pname = argv[0];
/*
 * Process options.
 */
        while ((c = getopt(argc, argv, "c:d:f:h")) != EOF) {
                switch (c) {
        /*
         * -c config -- specify configuration file
         */
                case 'c':
                        if (cnm != NULL) {
                                (void) fprintf(stderr,
                                        "%s: duplicate config name\n", Pname);
                                err = 1;
                        } else
                                cnm = optarg;
                        break;
        /*
         * -d device -- specify device
         */
                case 'd':
                        if (dnm != NULL) {
                                (void) fprintf(stderr,
                                        "%s: duplicate device name\n", Pname);
                                err = 1;
                        } else
                                dnm = optarg;
                        break;
        /*
         * -f font -- specify font
         */
                case 'f':
                        if (fnm != NULL) {
                                (void) fprintf(stderr,
                                        "%s: duplicate font name\n", Pname);
                                err = 1;
                        } else
                                fnm = optarg;
                        break;
        /*
         * -h -- request help
         */
                case 'h':
                        help++;
                        break;
        /*
         * unknown option
         */
                case '?':
                        err = 1;
                }
        }
/*
 * Handle file arguments.
 */
        if (optind >= argc)
                fc = 0;
        else {
                fc = argc;
                for (fx = optind; fx < fc; fx++) {
                        if (stat(argv[fx], &sbuf) != 0) {
                                (void) fprintf(stderr, "%s: can't find %s\n",
                                        Pname, argv[fx]);
                                err++;
                        }
                }
        }
/*
 * Read configuration file.
 */
        if (Readcf(cnm) == 0)
                err++;
/*
 * Validate device name.
 */
        if (dnm == NULL)
                dnm = Defdev;
        for (dp = Dp; dp; dp = dp->next) {
                if (strcmp(dnm, dp->nm) == 0)
                        break;
        }
        if (dp) {
                if (fnm == NULL && dp->f)
                        fnm = (dp->f)->nm;
        } else {
                if (dnm == NULL)
                        (void) fprintf(stderr, "%s: no device specified\n",
                                Pname);
                else
                        (void) fprintf(stderr, "%s: unknown device: %s\n",
                                Pname, dnm);
                err++;
                dp = NULL;
        }
/*
 * Validate font name.
 */
        if (fnm != NULL && dp) {
                for (fp = dp->f; fp; fp = fp->next) {
                        if (strcmp(fnm, fp->nm) == 0)
                                break;
                }
                if (fp == NULL) {
                        (void) fprintf(stderr,
                                "%s: font %s not defined for device %s\n",
                                Pname, fnm, dnm);
                        err++;
                }
        } else
                fp = NULL;
/*
 * Go no further if errors have been detected, or help has been requested.
 */
        if (err || help) {
                (void) fprintf(stderr,
                        "%s usage: [-c config ] [-d device]", Pname);
                (void) fprintf(stderr,
                        " [-f font] [file(s)]\n");
                (void) fprintf(stderr,
                        "\t-c config    specify configuration file path\n");
                if (Conf != NULL) {
                        (void) fprintf(stderr,
                                "\t             (currently using %s)\n", Conf);
                }
                (void) fprintf(stderr,
                        "\t-d device    specify device name\n");
                if (Defdev != NULL) {
                        (void) fprintf(stderr,
                                "\t             (default = %s)\n", Defdev);
                }
                (void) fprintf(stderr,
                        "\t-f font      specify font name\n");
                (void) fprintf(stderr,
                        "\tfile(s)      specify cawf -fe output files\n");
                (void) fprintf(stderr,
                        "\t             (default = standard input)\n");
                (void) fprintf(stderr,
                        "-d       -f (first font is default)\n");
                for (dp = Dp; dp; dp = dp->next) {
                        (void) fprintf(stderr, "%-8.8s ", dp->nm);
                        if ((fp = dp->f) == NULL)
                                (void) fprintf(stderr, "none\n");
                        else {
                                sep = "";
                                while (fp) {
                                        (void) fprintf(stderr,
                                                "%s%s", sep, fp->nm);
                                        sep = "|";
                                        fp = fp->next;
                                }
                                (void) putc('\n', stderr);
                        }
                }
                if (err)
                        exit(1);
                exit(0);
        }
/*
 * Issue font selection, if any, and set up font escape string pointers.
 */
        if (fp && fp->fi)
                fputs(fp->fi, stdout);
        Besc = dp->b;
        Iesc = dp->i;
        Resc = dp->r;
/*
 * Loop through the input files.
 */
        fx = optind;
        do {

        /*
         * If there are no files, use standard input (once).
         */
                if (fc == 0)
                        fs = stdin;
                else {
        /*
         * Open the named file.
         */
#ifdef  UNIX
                        if ((fs = fopen(argv[fx], "r")) == NULL)
#else
                        if ((fs = fopen(argv[fx], "rt")) == NULL)
#endif
                        {
                        (void) fprintf(stderr, "%s: can't open %s\n",
                                Pname, argv[fx]);
                        exit(1);
                        }
                }
        /*
         * Read and process the file.
         */
                while ( ! feof(fs) && (c = getc(fs)) != EOF) {
                        switch (c) {

                        case ESC:
                                Getec(fs);
                                if ( ! feof(fs) && (c = getc(fs)) != EOF)
                                        putchar(c);
                                break;
                        default:
                                Roman();
                                putchar(c);
                                break;
                        }
                }
                if (fc != 0)
                        (void) fclose(fs);
        } while (++fx < fc);
        exit(0);
}


/*
 * Bold() - set bold font
 */

static void
Bold()

{
        if (Font == BOLD)
                return;
        if (Besc)
                fputs(Besc, stdout);
        Font = BOLD;
}


/*
 * Convfont(dp, s) - convert a font for a device
 *
 * entry:
 *      dp = device structure pointer
 *      s  = font definition string -- "name=initialization"
 *
 * exit:
 *      return = 0 if errors detected
 */

static int
Convfont(dp, s)
        struct dev *dp;                 /* device structure pointer */
        char *s;                        /* font definition string */
{
        char *cp;                       /* temporary character pointer */
        char *fn;                       /* font name */
        struct font *fp;                /* font structure pointer */
        struct font *tfp;               /* temporary font structure pointer */
/*
 * Get the font name allocate space for it and allocate space for
 * a font structure.
 */
        if ((cp = strchr(s, '=')) == NULL) {
                (void) fprintf(stderr, "%s: bad %s font line format: %s\n",
                        Pname, dp->nm, s);
                return(0);
        }
        if ((fn = malloc(cp - s + 1)) == NULL) {
                (void) fprintf(stderr, "%s: no space for %s font name %s\n",
                        Pname, dp->nm, s);
                return(0);
        }
        (void) strncpy(fn, s, cp - s);
        fn[cp - s] = '\0';
        if ((fp = (struct font *)malloc(sizeof(struct font))) == NULL) {
                (void) fprintf(stderr, "%s: no space for %s font struct %s\n",
                        Pname, dp->nm, fn);
                return(0);
        }
/*
 * Fill in the font structure with the font name and the initialization
 * string.
 */
        fp->nm = fn;
        fp->next = NULL;
        if (dp->f == NULL)
                dp->f = fp;
        else {
                for (tfp = dp->f; tfp; tfp = tfp->next) {
                        if (tfp->next == NULL) {
                                tfp->next = fp;
                                break;

                        }
                }
        }
        if ((fp->fi = Convstr(cp + 1)) == NULL)
                return(0);
        return(1);
}


/*
 * Convstr(s) - convert a string
 *
 * entry:
 *      s = input string
 *
 * exit:
 *      return = converted string address
 *               NULL = error
 */

static char *
Convstr(s)
        char *s;                        /* input string */
{
        int c;                          /* character assembly */
        char *cp;                       /* temporary character pointer */
        char *em;                       /* error message */
        int i;                          /* temporary index */
        int l;                          /* length */
        char *r;                        /* result string */
/*
 * Make space for the result.
 */
        if ((r = malloc(strlen(s) + 1)) == NULL) {
                (void) fprintf(stderr, "%s: out of string space at %s\n",
                        Pname, r);
                return(NULL);
        }
/*
 * Copy the input string to the result, processing '\\' escapes.
 */
        for (cp = r; *s;) {
                switch (*s) {

                case '\\':
                        s++;
                        if (*s >= '0' && *s <= '7') {
                /*
                 * '\xxx' -- octal form
                 */
                                for (c = i = 0; i < 3; i++, s++) {
                                        if (*s < '0' || *s > '7') {
                                                em = "non-octal char";
bad_string:
                                                (void) fprintf(stderr,
                                                        "%s: %s : %s\n",
                                                        Pname, em, r);
                                                return(NULL);
                                        }
                                        c = (c << 3) + *s - '0';
                                }
                                if (c > 0377) {
                                        em = "octal char > 0377";
                                        goto bad_string;
                                }
                                *cp++ = c;
                        } else if (*s == 'x') {
                /*
                 * '\xyy' -- hexadecimal form
                 */
                                s++;
                                for (c = i = 0; i < 2; i++, s++) {
                                        if ( ! isascii(*s) && ! isalpha(*s)
                                        &&   ! isdigit(*s)) {
non_hex_char:
                                                em = "non-hex char";
                                                goto bad_string;
                                        }
                                        c = c << 4;
                                        if (*s >= '0' && *s <= '9')
                                                c += *s - '0';
                                        else if ((*s >= 'a' && *s <= 'f')
                                             ||  (*s >= 'A' && *s <= 'F'))
                                                c += *s + 10 -
                                                     (isupper(*s) ? 'A' : 'a');
                                        else
                                                goto non_hex_char;
                                }
                                *cp++ = c;
                        } else if (*s == 'E' || *s == 'e') {
                /*
                 * '\E' or '\e' -- ESCape
                 */
                                *cp++ = ESC;
                        } else if (*s == '\0') {
                                em = "no char after \\";
                                goto bad_string;
                        } else
                /*
                 * escaped character (for some reason)
                 */
                                *cp++ = *s++;
                        break;
        /*
         * Copy a "normal" character.
         */
                default:
                        *cp++ = *s++;
                }
        }
        *cp = '\0';
        return(r);
}


/*
 * Getec(fs) - get ESCape follower character
 *
 * entry:
 *      fs = file stream
 */

static void
Getec(fs)
        FILE *fs;                       /* file stream */
{
        register int c;

        if (feof(fs) || (c = getc(fs)) == EOF)
                return;
        switch (c) {

        case ROMAN:
                Roman();
                break;
        case ITALIC:
                Italic();
                break;
        case BOLD:
                Bold();
                break;
        default:
                (void) fprintf (stderr,
                        "%s: unknown escape sequence ESC-0x%x\n",
                        Pname, c);
                break;
        }
}


/*
 * Italic() - set italic font
 */

static void
Italic()

{
        if (Font == ITALIC)
                return;
        if (Iesc)
                fputs(Iesc, stdout);
        Font = ITALIC;
}


/*
 * Roman() - set Roman font
 */

static void
Roman()

{
        if (Font == ROMAN)
                return;
        if (Resc)
                fputs(Resc, stdout);
        Font = ROMAN;
}


/*
 * Readcf(p) - read configuration file
 *
 * entry:
 *      p = configuration file path
 *
 * exit:
 *      return = 0 if error detected
 */

static int
Readcf(p)
        char *p;                        /* configuration file path */
{
        FILE *fs;                       /* file stream */
        char *dn;                       /* device name */
        struct dev *dp;                 /* device structure pointer */
        int err = 0;                    /* errror count */
        int l;                          /* length */
        char line[MAXLINE];             /* line buffer */
        char *s;                        /* temporary string pointer */
/*
 * If a path is supplied, use it.
 */
        if (p)
                Conf = p;
        else {

        /*
         * Use the CAWFLIB environment if it is defined.
         */
                if ((Conf = getenv("CAWFLIB")) == NULL) 
                        Conf = CAWFLIB;
                l = strlen(Conf) + 1 + strlen(p ? p : CONFIG) + 1;
                if ((s = malloc(l)) == NULL) {
                        (void) fprintf(stderr, "%s: no space for %s name\n",
                                Pname,
                                p ? p : CONFIG);
                        return(0);
                }
                (void) sprintf(s, "%s/%s", Conf, p ? p : CONFIG);
                Conf = s;
        }
/*
 * Open the configuration file.
 */
#ifdef  UNIX
        if ((fs = fopen(Conf, "r")) == NULL)
#else
        if ((fs = fopen(Conf, "rt")) == NULL)
#endif
        {
                (void) fprintf(stderr, "%s: can't open config file: %s\n",
                        Pname, Conf);
                return(0);
        }
        *line = ' ';
/*
 * Look for a device definition line -- a line that begins with a name.
 */
        while ( ! feof(fs)) {
                if (isascii(*line) && (isspace(*line) || *line == '#')) {
                        (void) fgets(line, MAXLINE, fs);
                        continue;
                }
                if ((s = strrchr(line, '\n')) != NULL)
                        *s = '\0';
                else
                        line[MAXLINE-1] = '\0';
        /*
         * Allocate space for the name and the device structure.
         */
                if ((dn = malloc(strlen(line) + 1)) == NULL) {
                        (void) fprintf(stderr,
                                "%s: no string space for device %s\n",
                                Pname, line);
                        return(0);
                }
                (void) strcpy(dn, line);
                if ((dp = (struct dev *)malloc(sizeof(struct dev))) == NULL) {
                        (void) fprintf(stderr,
                                "%s: no space for structure for device %s\n",
                                Pname, dn);
                        return(0);
                }
                dp->next = Dp;
                Dp = dp;
                Defdev = dp->nm = dn;
                dp->b = dp->i = dp->r = NULL;
                dp->f = NULL;
        /*
         * Read the parameter lines for the device.
         */
                while (fgets(line, MAXLINE, fs) != NULL) {
                        if ( ! isascii(*line) || *line != '\t')
                                break;
                        if ( ! isascii(line[1]) || ! isalpha(line[1])
                        ||   line[2] != '=')
                                break;
                        if ((s = strrchr(line, '\n')) != NULL)
                                *s = '\0';
                        else
                                line[MAXLINE-1] = '\0';
                        switch (line[1]) {
                /*
                 * \tb=<bolding_string>
                 */
                        case 'b':
                                if ((dp->b = Convstr(&line[3])) == NULL)
                                        err++;
                                break;
                /*
                 * \ti=<italicization_string>
                 */
                        case 'i':
                                if ((dp->i = Convstr(&line[3])) == NULL)
                                        err++;
                                break;
                /*
                 * \tr=<return_to_Roman_string>
                 */
                        case 'r':
                                if ((dp->r = Convstr(&line[3])) == NULL)
                                        err++;
                                break;
                /*
                 * \tf=<font_name>=<font_initialization_string>
                 */
                        case 'f':
                                if (Convfont(dp, &line[3]) == 0)
                                        err++;
                                break;
                /*
                 * ????
                 */
                        default:
                                (void) fprintf(stderr,
                                        "%s: unknown device %s line %s\n",
                                        Pname, dp->nm, line);
                                err++;
                        }
                }
        }
        (void) fclose(fs);
        if (err)
                return(0);
        return(1);
}
