/********************************************************************
 *                                                                  *
 *  Title:  pfm2afm - Convert Windows .pfm files to .afm files      *
 *                                                                  *
 *  Author: Ken Borgendale   10/9/91  Version 1.0                   *
 *                                                                  *
 *  Function:                                                       *
 *      Convert a Windows .pfm (Printer Font Metrics) file to a     *
 *      .afm (Adobe Font Metrics) file.  The purpose of this is     *
 *      to allow fonts put out for Windows to be used with OS/2.    *
 *                                                                  *
 *  Syntax:                                                         *
 *      pfm2afm  infile  [outfile] -a                               *
 *                                                                  *
 *  Copyright:                                                      *
 *      pfm2afm - Copyright (C) IBM Corp., 1991                     *
 *                                                                  *
 *      This code is released for public use as long as the         *
 *      copyright remains intact.  This code is provided asis       *
 *      without any warrenties, express or implied.                 *
 *                                                                  *
 *  Notes:                                                          *
 *      1. Much of the information in the original .afm file is     *
 *         lost when the .pfm file is created, and thus cannot be   *
 *         reconstructed by this utility.  This is especially true  *
 *         of data for characters not in the Windows character set. *
 *                                                                  *
 *      2. This module is coded to be compiled by the MSC 6.0.      *
 *         For other compilers, be careful of the packing of the    *
 *         PFM structure.                                           *
 *                                                                  *
 ********************************************************************/


/********************************************************************
 *  Modified:  Russell Lang <rjl@eng.monash.edu.au>                 *
 *             1994-01-06  Version 1.1                              *
 *  Compiles with EMX/GCC                                           *
 *  Changed to AFM 3.0                                              *
 *  Put PFM Copyright in Notice instead of Comment                  *
 *  Added ItalicAngle                                               *
 *  Added UnderlinePosition                                         *
 *  Added UnderlineThickness                                        *
 *                                                                  *
 *  Modified 1995-03-10  rjl (fixes from Norman Walsh)              *
 *  Dodge compiler bug when creating descender                      *
 ********************************************************************/


/********************************************************************
 *  Modified:  Olivier Plathey <olivier@fpdf.org>                   *
 *             2002-08-10  Version 1.11                             *
 *  Compiles with MinGW                                             *
 *  Output Descender as negative value                              *
 *  Removed double spaces in character entries                      *
 *                                                                  *
 *             2005-03-12  Version 1.12                             *
 *  Doubled size of BUFSIZE                                         *
 ********************************************************************/

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "pfm2afm.h"

#define BUFSIZE 8192
/*
 *  Function Prototypes
 */
void  help (void);
void  parseargs (int argc, uchar * * argv);
void  openpfm(void);
void  openafm(void);
void  putheader(void);
void  putchartab(void);
void  outchar(int code, ushort width, const uchar * name);
void  putkerntab(KERN * kerntab, int kerncnt);
void  puttrailer(void);
void  outval(int val);
void  outreal(float val);

/*
 *  Global variables
 */
FILE   * inf;                /* Input file */
FILE   * outf;               /* Output file */
uchar  infname[272];         /* Input file name */
uchar  outfname[272];        /* Output file name */
uchar  * buffer;             /* .pfm read buffer */
PFM    * pfm;                /* .pfm header */
PSX    * psx;                /* Metrics extension */

uchar    debugflag;          /* Debug information flag */
uchar    allflag;
uchar    isMono;             /* Font is mono-spaced */

/*
 *  Do the function
 */
MAINENT main(int argc, uchar * *argv) {

    /* Parse arguments */
    parseargs(argc, argv);

    /* Open and check input file */
    openpfm();

    /* Make output file name and open */
    openafm();

    /* Put out header information */
    putheader();

    /* Put out character table */
    putchartab();

    /* Put out kerning table */
    if (pfm->kernpairs) {
        putkerntab((KERN *)(buffer+pfm->kernpairs+2),
                   *(ushort *)(buffer+pfm->kernpairs));
    }
    if (pfm->kerntrack) { /* rjl */
	fprintf(stderr, "Ignoring track kern table\n");
    }

    /* Put out trailer line */
    puttrailer();

    /* Cleanup */
    if (buffer)
        free(buffer);
    fclose(inf);
    fclose(outf);
    return 0;
}

/*
 *  Put out normal help
 */
void  help (void) {
    puts("\npfm2afm - Convert Windows pfm to afm - Version 1.11\n");
    puts("This utility converts Windows pfm files for Adobe type 1 fonts");
    puts("to afm files for use on OS/2.  This allows fonts created for");
    puts("Windows, and shipped without the afm file to be used on OS/2.\n");
    puts("pfm2afm  infile  [outfile]  -opts");
    puts("    The extension .pfm is added to the infile if it has none.");
    puts("    The outfile is defaulted from the input file name.");
    puts("    -a = All codepoints in range");
    puts("\nNote that pfm files are missing some of the data necessary to");
    puts("construct afm files, so the conversion may not be perfect.\n");
    puts("Ken Borgendale  -  kwb@betasvm2.vnet.ibm.com\n");
    puts("Russell Lang    -  rjl@monu1.cc.monash.edu.au\n");
    exit (1);
}


/*
 *  Parse arguments.  This is the full arg treatment, which is sort of
 *  overkill for one option, but it allows more to be added later.
 */
void  parseargs (int argc, uchar * * argv) {
    uchar  swchar;
    int    argcnt;
    int    filecnt;
    uchar * argp;

    argcnt = 1;
    filecnt = 0;
    /* Read the arguments and decide what we are doing */
    while (argcnt<argc) {
        argp = argv[argcnt];
        /* Check for switches.  Files may not start with - or / */
        if (*argp == '-' || *argp == OPTSEP) {
            /* Process switches */
            swchar = (uchar)tolower(argp[1]);
            argp += 2;
            switch (swchar) {
            case '?':
                help();      /* Does not return */

            /* All codepoints */
            case 'a':
                allflag = 0;
                break;

            /* Debug option */
            case 'd':
                debugflag = 1;
                break;

            default:
                fputs("Unknown options: ", stderr);
                fputs(argp-2, stderr);
                fputc('\n', stderr);
            }
        } else {
            if (*argp=='?') {
                help();      /* Does not return */
            }
            switch(++filecnt) {
            case 1:
                strcpy(infname, argp);
                break;
            case 2:
                strcpy(outfname, argp);
                break;
            default:
                fputs("Extra parameter ignored: ", stderr);
                fputs(argp, stderr);
                fputc('\n', stderr);
            }
        }
        argcnt++;
    }

    /* We require the input file name */
    if (!filecnt) help();
}


/*
 *  Open the .pfm file and check it
 */
void  openpfm(void) {
    uchar   * cp;
    int       len;

    /* Check for a file extension */
    cp = infname+strlen(infname)-1;
    while (cp>=infname && *cp!='.' && *cp!='\\' && *cp!='/' && *cp!=':')
       cp--;
    if (*cp!='.')
        strcat(infname, ".pfm");
    /* Open the file */
    inf = fopen(infname, "rb");
    if (!inf) {
        fputs("Unable to open input file - ", stderr);
        fputs(infname, stderr);
        fputc('\n', stderr);
        exit(4);
    }
    /* Read the file */
    buffer = malloc(BUFSIZE);
    len = fread(buffer, 1, BUFSIZE, inf);
    if (len<256 || len==BUFSIZE) {
        fputs("Input file read error - ", stderr);
        fputs(infname, stderr);
        fputc('\n', stderr);
        exit(6);
    }
    /* Do consistency check */
    pfm = (PFM *) buffer;
    if (len != (int)pfm->len &&  /* Check length field matches file length */
        pfm->extlen != 30 &&     /* Check length of PostScript extension   */
        pfm->fontname>75 && pfm->fontname<512) {  /* Font name specified */
        fputs("Not a valid Windows type 1 .pfm file - ", stderr);
        fputs(infname, stderr);
        fputc('\n', stderr);
        exit(6);
    }
}

/*
 *  Create the .afm file
 */
void  openafm(void) {
    uchar  * cp;

    /* Add .pfm if there is none */
    if (!*outfname) {
        strcpy(outfname, infname);
        cp = outfname+strlen(outfname)-1;
        while (cp >= outfname && *cp!='.' && *cp!='\\' && *cp!='/' && *cp!=':')
           cp--;
        if (*cp=='.') *cp=0;
        strcat(outfname, ".afm");
    }
    /* Open the file */
    outf = fopen(outfname, "w");
    if (!outf) {
        fputs("Unable to open output file - ", stderr);
        fputs(outfname, stderr);
        fputc('\n', stderr);
        exit(5);
    }
}

/*
 *  Put out the header of the .afm file
 */
void  putheader(void) {
    uchar * cp;
    int temp;  /* rjl 1995-03-10 */

    fputs("StartFontMetrics 3.0\n", outf);
    if (*pfm->copyright) {
        fputs("Notice ", outf);
        fputs(pfm->copyright, outf);
        fputc('\n', outf);
    }
    fputs("FontName ", outf);
    fputs(buffer+pfm->fontname, outf);
    fputs("\nEncodingScheme ", outf);
    if (pfm->charset) {
        fputs("FontSpecific\n", outf);
    } else {
        fputs("AdobeStandardEncoding\n", outf);
    }
    /*
     * The .pfm is missing full name, so construct from font name by
     * changing the hyphen to a space.  This actually works in a lot
     * of cases.
     */
    fputs("FullName ", outf);
    cp = buffer+pfm->fontname;
    while (*cp) {
        if (*cp=='-') *cp=' ';
        fputc(*cp, outf);
        cp++;
    }
    if (pfm->face) {
        fputs("\nFamilyName ", outf);
        fputs(buffer+pfm->face, outf);
    }

    fputs("\nWeight ", outf);
    if (pfm->weight>475) fputs("Bold", outf);
    else if (pfm->weight<325 && pfm->weight)
        fputs("Light", outf);
    else fputs("Medium", outf);

    /*
     *  The mono flag in the pfm actually indicates whether there is a
     *  table of font widths, not if they are all the same.
     */
    fputs("\nIsFixedPitch ", outf);
    if (!(pfm->kind&1) ||                  /* Flag for mono */
        pfm->avgwidth == pfm->maxwidth ) {  /* Avg width = max width */
        fputs("true", outf);
        isMono = 1;
    } else {
        fputs("false", outf);
        isMono = 0;
    }

    /*
     * The font bounding box is lost, but try to reconstruct it.
     * Much of this is just guess work.  The bounding box is required in
     * the .afm, but is not used by the PM font installer.
     */
    psx = (PSX *)(buffer+pfm->psext);
    fputs("\nFontBBox", outf);
    if (isMono) outval(-20);      /* Just guess at left bounds */
    else outval(-100);
    temp = psx->descender; /* rjl 1995-03-10 */
    temp = -(temp+5); /* rjl 1995-03-10 */
    outval(temp);  /* Descender is given as positive value */ /* rjl 1995-03-10 */
    /*     outval(-(psx->descender+5));  /* Descender is given as positive value */
    outval(pfm->maxwidth+10);
    outval(pfm->ascent+5);

    /*
     * Give other metrics that were kept
     */
    fputs("\nCapHeight", outf);
    outval((int)psx->capheight);
    fputs("\nXHeight", outf);
    outval((int)psx->xheight);
    fputs("\nDescender", outf);
    outval((int)-psx->descender); /* output negative value*/
    fputs("\nAscender", outf);
    /* outval((int)psx->ascender); */
    outval((int)pfm->ascent); /* rjl */
    /* extra keys added by rjl */
    if (psx->len >= sizeof(psx)) {
	fputs("\nItalicAngle", outf);
        outreal(psx->slant/10.0);
	fputs("\nUnderlinePosition", outf);
        outval((int)-psx->underlineoffset);
	fputs("\nUnderlineThickness", outf);
        outval((int)psx->underlinewidth);
    }
    fputc('\n', outf);
}

/*
 *  Put out the character tables.  According to the .afm spec, the
 *  characters must be put out sorted in encoding order.
 *
 *  Most Windows .pfm files have the characters in the range 20-ff in
 *  the Windows code page (819 + quotes).
 */
void  putchartab(void) {
    int    count, i, j;
    ushort spwidth;
    ushort * ctab;
    uchar  back[256];

    /*
     * Compute the count by getting rid of non-existant chars.  This
     * is complicated by the fact that Windows encodes the .pfm file
     * with a space metric for non-existant chars.
     */
    memset(back, 0, 256);
    count = pfm->lastchar - pfm->firstchar + 1;
    spwidth = 0;
    /* Compute width of space */
    ctab = (ushort *)(buffer+pfm->chartab);
    if (pfm->firstchar>=' ' && pfm->lastchar<=' ') {
        spwidth = ctab[' '-pfm->firstchar];
    }

    if (!pfm->charset) {
        /*
         *  Loop thru the chars, deleting those that we presume
         *  do not really exist.
         */
        for (i=pfm->firstchar; i<=(int)pfm->lastchar; i++) {
            if (Win2PSStd[i]) {
                back[Win2PSStd[i]] = (uchar)i;
            } else {
                if (!allflag) {
                    if (*ctab==spwidth) {   /* Default width */
                        if (!(WinClass[i]&1)) {
                            *ctab = 0;
                            count--;
                        }
                    } else {                /* Not default width */
                        if (!WinClass[i]) {
                            *ctab = 0;
                            count--;
                        }
                    }
                }
            }
            ctab++;
        }
    }

    /* Put out the header */
    fputs("StartCharMetrics", outf);
    outval(count);
    fputc('\n', outf);

    /* Put out all encoded chars */
    if (pfm->charset) {
    /*
     * If the charset is not the Windows standard, just put out
     * unnamed entries.
     */
        ctab = (ushort *)(buffer+pfm->chartab);
        for (i=pfm->firstchar; i<=(int)pfm->lastchar; i++) {
            if (*ctab) {
                outchar(i, *ctab, NULL);
            }
            ctab++;
        }
    } else {
        ctab = (ushort *)(buffer+pfm->chartab);
        for (i=0; i<256; i++) {
            j = back[i];
            if (j) {
                outchar(i, ctab[j-pfm->firstchar], WinChars[j]);
                ctab[j-pfm->firstchar] = 0;
            }
        }
        /* Put out all non-encoded chars */
        ctab = (ushort *)(buffer+pfm->chartab);
        for (i=pfm->firstchar; i<=(int)pfm->lastchar; i++) {
            if (*ctab) {
                outchar(-1, *ctab, WinChars[i]);
            }
            ctab++;
        }
    }
    /* Put out the trailer */
    fputs("EndCharMetrics\n", outf);
}

/*
 *  Output a character entry
 */
void  outchar(int code, ushort width, const uchar * name) {
    fputs("C", outf);
    outval(code);
    fputs(" ; WX", outf);
    outval(width);
    if (name) {
        fputs(" ; N ", outf);
        fputs(name, outf);
    }
    fputs(" ;\n", outf);
}

/*
 *  Put out the kerning tables
 */
void  putkerntab(KERN * kerntab, int kerncnt) {
    int    count, i;
    KERN * kp;

    /* Count non-zero kern pairs */
    count = kerncnt;
    kp = kerntab;
    for (i=0; i<kerncnt; i++) {
        if (!kp->kern)
            count--;
        kp++;
    }

    /* Put out header */
    fputs("StartKernData\nStartKernPairs", outf);
    outval(count);
    fputc('\n', outf);

    /* Put out each non-zero pair */
    kp = kerntab;
    while (kerncnt) {
        if (kp->kern) {
            fputs("KPX ", outf);
            fputs(WinChars[kp->first], outf);
            fputc(' ', outf);
            fputs(WinChars[kp->second], outf);
            outval((int)kp->kern);
            fputc('\n', outf);
        }
        kp++;
        kerncnt--;
    }

    /* Put out trailer */
    fputs("EndKernPairs\nEndKernData\n", outf);
}

/*
 *  Put out the trailer of the .afm file
 */
void  puttrailer(void) {
    fputs("EndFontMetrics\n", outf);
}

/*
 *  Output a decimal value
 */
void outval(int v) {
    char chx[16];
#ifdef unix
    sprintf(chx, "%d", v);  /* rjl 1995-03-10 */
#else
    itoa(v, chx, 10);
#endif
    fputc(' ', outf);
    fputs(chx, outf);
}

/*
 *  Output a real value
 */
void outreal(float v) {
    fprintf(outf," %g",v);
}
