/*
 * file: font2ps.c
 *
 * compile: gcc -o font2ps font2ps.c -lX11
 *
 * (c) P. Kleiweg 1997
 *
 */

#include <ctype.h>
#include <errno.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <values.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>

typedef enum { DEFAULT, LATIN, SYMBOL } ENCODING;

ENCODING
    Encoding = DEFAULT;

char
    *names [256],
    *ISOLatin1 [] = {
        NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
        NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
        NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
        NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
        "space", "exclam", "quotedbl", "numbersign",
        "dollar", "percent", "ampersand", "quoteright",
        "parenleft", "parenright", "asterisk", "plus",
        "comma", "minus", "period", "slash",
        "zero", "one", "two", "three",
        "four", "five", "six", "seven",
        "eight", "nine", "colon", "semicolon",
        "less", "equal", "greater", "question",
        "at", "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", "bracketleft",
        "backslash", "bracketright", "asciicircum", "underscore",
        "quoteleft", "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", "braceleft",
        "bar", "braceright", "asciitilde", NULL,
        NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
        NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
        "dotlessi", "grave", "acute", "circumflex",
        "tilde", "macron", "breve", "dotaccent",
        "dieresis", NULL, "ring", "cedilla",
        NULL, "hungarumlaut", "ogonek", "caron",
        "space", "exclamdown", "cent", "sterling",
        "currency", "yen", "brokenbar", "section",
        "dieresis", "copyright", "ordfeminine", "guillemotleft",
        "logicalnot", "hyphen", "registered", "macron",
        "degree", "plusminus", "twosuperior", "threesuperior",
        "acute", "mu", "paragraph", "periodcentered",
        "cedilla", "onesuperior", "ordmasculine", "guillemotright",
        "onequarter", "onehalf", "threequarters", "questiondown",
        "Agrave", "Aacute", "Acircumflex", "Atilde",
        "Adieresis", "Aring", "AE", "Ccedilla",
        "Egrave", "Eacute", "Ecircumflex", "Edieresis",
        "Igrave", "Iacute", "Icircumflex", "Idieresis",
        "Eth", "Ntilde", "Ograve", "Oacute",
        "Ocircumflex", "Otilde", "Odieresis", "multiply",
        "Oslash", "Ugrave", "Uacute", "Ucircumflex",
        "Udieresis", "Yacute", "Thorn", "germandbls",
        "agrave", "aacute", "acircumflex", "atilde",
        "adieresis", "aring", "ae", "ccedilla",
        "egrave", "eacute", "ecircumflex", "edieresis",
        "igrave", "iacute", "icircumflex", "idieresis",
        "eth", "ntilde", "ograve", "oacute",
        "ocircumflex", "otilde", "odieresis", "divide",
        "oslash", "ugrave", "uacute", "ucircumflex",
        "udieresis", "yacute", "thorn", "ydieresis"},
    *Symbol [] = {
        NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
        NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
        NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
        NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
        "space", "exclam", "universal", "numbersign",
        "existential", "percent", "ampersand", "suchthat",
        "parenleft", "parenright", "asteriskmath", "plus",
        "comma", "minus", "period", "slash",
        "zero", "one", "two", "three",
        "four", "five", "six", "seven",
        "eight", "nine", "colon", "semicolon",
        "less", "equal", "greater", "question",
        "congruent", "Alpha", "Beta", "Chi",
        "Delta", "Epsilon", "Phi", "Gamma",
        "Eta", "Iota", "theta1", "Kappa",
        "Lambda", "Mu", "Nu", "Omicron",
        "Pi", "Theta", "Rho", "Sigma",
        "Tau", "Upsilon", "sigma1", "Omega",
        "Xi", "Psi", "Zeta", "bracketleft",
        "therefore", "bracketright", "perpendicular", "underscore",
        "radicalex", "alpha", "beta", "chi",
        "delta", "epsilon", "phi", "gamma",
        "eta", "iota", "phi1", "kappa",
        "lambda", "mu", "nu", "omicron",
        "pi", "theta", "rho", "sigma",
        "tau", "upsilon", "omega1", "omega",
        "xi", "psi", "zeta", "braceleft",
        "bar", "braceright", "similar", NULL,
        NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
        NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
        NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
        NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
        NULL, "Upsilon1", "minute", "lessequal",
        "fraction", "infinity", "florin", "club",
        "diamond", "heart", "spade", "arrowboth",
        "arrowleft", "arrowup", "arrowright", "arrowdown",
        "degree", "plusminus", "second", "greaterequal",
        "multiply", "proportional", "partialdiff", "bullet",
        "divide", "notequal", "equivalence", "approxequal",
        "ellipsis", "arrowvertex", "arrowhorizex", "carriagereturn",
        "aleph", "Ifraktur", "Rfraktur", "weierstrass",
        "circlemultiply", "circleplus", "emptyset", "intersection",
        "union", "propersuperset", "reflexsuperset", "notsubset",
        "propersubset", "reflexsubset", "element", "notelement",
        "angle", "gradient", "registerserif", "copyrightserif",
        "trademarkserif", "product", "radical", "dotmath",
        "logicalnot", "logicaland", "logicalor", "arrowdblboth",
        "arrowdblleft", "arrowdblup", "arrowdblright", "arrowdbldown",
        "lozenge", "angleleft", "registersans", "copyrightsans",
        "trademarksans", "summation", "parenlefttp", "parenleftex",
        "parenleftbt", "bracketlefttp", "bracketleftex", "bracketleftbt",
        "bracelefttp", "braceleftmid", "braceleftbt", "braceex",
        "Apple", "angleright", "integral", "integraltp",
        "integralex", "integralbt", "parenrighttp", "parenrightex",
        "parenrightbt", "bracketrighttp", "bracketrightex", "bracketrightbt",
        "bracerighttp", "bracerightmid", "bracerightbt", NULL};

unsigned
    FontPage;

int
    nr_of_bits,
    maxwidth,
    minlbearing,
    maxrbearing,
    maxascent,
    maxdescent,
    width,
    lbearing,
    rbearing,
    ascent,
    descent,
    screen;
Pixmap
    bitmap;
Display
    *display;
XFontStruct
    *font_struct;
char
    *font_name,
    *tmpfilename,
    *programname,
    *no_mem_buffer,
    no_memory [] = "Out of memory";
Window
    rootwindow;
GC
    bitmap_gc;

void
    remove_entry (int index),
    get_programname (char const *argv0),
    syntax (void),
    *s_malloc (size_t size),
    errit (char const *format, ...);

char
    invert (char c),
    *s_strdup (char const *s);

Display 
    *ConnectToServer (char display_name [], int *screen, Window *rootwindow);

unsigned
    offset (int index);

int main (int argc, char *argv [])
{
    unsigned
        n;
    int
        i,
        c,
        retval;
    unsigned char
        s [1];
    FILE
        *fp;
    XChar2b
        s16 [1];

    no_mem_buffer = (char *) malloc (1024);

    get_programname (argv [0]);
    if (argc < 2 || argc > 3)
        syntax ();
    if (argc == 2) {
        nr_of_bits = 8,
        font_name = argv [1];
    } else {
        if (! strcmp (argv [1], "-l")) {
            Encoding = LATIN;
            nr_of_bits = 8;
        } else if (! strcmp (argv [1], "-s")) {
            Encoding = SYMBOL;
            nr_of_bits = 8;
        } else {
            FontPage = (unsigned) strtol (argv [1], NULL, 10);
            nr_of_bits = 16;
        }
        font_name = argv [2];
    }

    for (i = 0; i < 256; i++) 
        if (Encoding == LATIN)
            names [i] = ISOLatin1 [i];
        else if (Encoding == SYMBOL)
            names [i] = Symbol [i];
        else if (nr_of_bits == 16) {
            names [i] = (char *) s_malloc (9 * sizeof (char));
            sprintf (names [i], "c%u-%i", FontPage, i);
        } else {
            names [i] = (char *) s_malloc (5 * sizeof (char));
            sprintf (names [i], "c%i", i);
        }

    tmpfilename = tmpnam (NULL);

    display = ConnectToServer (getenv ("DISPLAY"), &screen, &rootwindow);
 
    font_struct = XLoadQueryFont (display, font_name);
    if (! font_struct)
        errit ("Unable to load font: %s", font_name);

    if (font_struct->min_byte1 < font_struct->max_byte1) {
        if (nr_of_bits != 16)
            errit (
                "This is a 16 bit font\n"
                "You need to give a page number betweeen %u and %u",
                 font_struct->min_byte1,
                 font_struct->max_byte1
            );
        if (FontPage < font_struct->min_byte1 || FontPage > font_struct->max_byte1)
            errit (
                "Illegal page number %u\n"
                "You need to give a page number betweeen %u and %u",
                 FontPage,
                 font_struct->min_byte1,
                 font_struct->max_byte1
            );
    } else {
        if (nr_of_bits == 16)
            errit ("This is not a 16 bit font");
        FontPage = font_struct->min_byte1;
    }

    maxwidth = 0;
    minlbearing = MAXINT;
    maxrbearing = maxascent = maxdescent = -MAXINT;
    for (i = font_struct->min_char_or_byte2; i <= font_struct->max_char_or_byte2; i++) {
        n = offset (i);
        width    = font_struct->per_char [n].width;
        lbearing = font_struct->per_char [n].lbearing;
        rbearing = font_struct->per_char [n].rbearing;
        ascent   = font_struct->per_char [n].ascent;
        descent  = font_struct->per_char [n].descent;
        if (width > maxwidth)
            maxwidth = width;
        if (lbearing < minlbearing)
            minlbearing = lbearing;
        if (rbearing > maxrbearing)
            maxrbearing = rbearing;
        if (ascent > maxascent)
            maxascent = ascent;
        if (descent > maxdescent)
            maxdescent = descent;
    }

    fputs (
        "%!PS\n"
        "\n"
        "10 dict begin\n"
        "\n",
        stdout
    );
    printf ("/FontName (%s) def\n", font_name);
    fputs (
        "/FontType 3 def\n"
        "/FontMatrix [1 0 0 1 0 0] def\n",
        stdout
    );

    printf (
        "/FontBBox [%i %i %i %i] def\n",
         minlbearing,
         -maxdescent,
         maxrbearing,
         maxascent
     );

    fputs (
        "/CharProcs 257 dict def\n"
        "CharProcs begin\n"
        "  /.notdef {\n"
        "    0 0 0 0 0 0 setcachedevice\n"
        "  } bind def\n",
        stdout
    );

    bitmap = XCreatePixmap (
        display,
        rootwindow,
        maxrbearing - minlbearing,
        maxascent + maxdescent,
        1
    );
    if (bitmap == (Pixmap) None)
        errit ("Creating bitmap");
    bitmap_gc = XCreateGC (display, bitmap, 0L, 0L);
    XSetFont (display, bitmap_gc, font_struct->fid);
    XSetBackground (display, bitmap_gc, 0);
    for (i = 0; i < 256; i++) {
        if (! names [i])
            continue;
        if (Encoding == LATIN && i == 160)  /* 160 = 32 = space */
            continue;
        if (i < font_struct->min_char_or_byte2
         || i > font_struct->max_char_or_byte2
        )  {
            remove_entry (i);
            continue;
        }
        n = offset (i);
        width    = font_struct->per_char [n].width;
        lbearing = font_struct->per_char [n].lbearing;
        rbearing = font_struct->per_char [n].rbearing;
        ascent   = font_struct->per_char [n].ascent;
        descent  = font_struct->per_char [n].descent;
        if ((lbearing >= rbearing || -descent >= ascent) && width == 0) {
            remove_entry (i);
            continue;
        }
        printf (
            "  /%s {\n"
            "    %i 0 %i %i %i %i setcachedevice\n",
            names [i],
            width,
            lbearing,
            -descent,
            rbearing,
            ascent
        );
        if (lbearing < rbearing && -descent < ascent) {
            XSetForeground (display, bitmap_gc, 0);
            XFillRectangle (
                display,
                bitmap,
                bitmap_gc,
                0,
                0,
                maxrbearing - minlbearing,
                maxascent + maxdescent
            );
            XSetForeground (display, bitmap_gc, 1);
            if (nr_of_bits == 16) {
                s16 [0].byte2 = (unsigned) i;
                s16 [0].byte1 = (unsigned) FontPage;
                XDrawString16 (display, bitmap, bitmap_gc, -lbearing, ascent, s16, 1);
	    } else {
                s [0] = i;
                XDrawString (display, bitmap, bitmap_gc, -lbearing, ascent, s, 1);
	    }
            retval = XWriteBitmapFile (
                display,
                tmpfilename,
                bitmap,
                rbearing - lbearing,
                ascent + descent,
                -1,
                -1
            );
            if (retval == BitmapOpenFailed)
                errit ("Creating Bitmapfile \"%s\" failed", tmpfilename);
            if (retval == BitmapNoMemory)
                errit ("No memory for writing bitmap");
	
            printf (
                "    %i %i\n"
                "    true\n"
                "    [1 0 0 -1 %i %i]\n"
                "    {<",
                rbearing - lbearing,
                ascent + descent,
                -lbearing,
                ascent
            );
            fp = fopen (tmpfilename, "r");
            if (! fp)
                errit ("Opening file \"%s\": %s", tmpfilename, strerror (errno));
            while (fgetc (fp) != '{')
                ;
            while ((c = fgetc (fp)) != EOF) {
                switch (c) {
	            case '0':
                        fgetc (fp);
                        c = fgetc (fp);
                        fputc (invert (fgetc (fp)), stdout);
                        fputc (invert (c), stdout);
                        break;
                    case ',':
                    case '}':
                    case ';':
                    case ' ':
                        break;
        	        default:
                        fputc (c, stdout);
                }
            }
            fclose (fp);
            unlink (tmpfilename);
            fputs (
                "    >}\n"
                "    imagemask\n",
                stdout
            );
        }
        fputs (
            "  } bind def\n",
            stdout
        );
    }

    if (names [0]) {
        for (i = 1; i < 256; i++)
            if (! names [i])
                break;
        if (i == 256)
            fprintf (stderr, "\nWARNING: character %s not included in Encoding\n\n", names [0]);
        else {
            names [i] = names [0];
            fprintf (stderr, "\nWARNING: character %s moved to position %i in Encoding\n\n", names [0], i);
        }
    }

    fputs (
        "end\n"
        "\n"
        "/Encoding [\n"
        "/.notdef ",
        stdout
    );
    for (i = 1; i < 256; i++)
        printf (
            "/%s %s",
            names [i] ? names [i] : ".notdef",
            ((i % 4) == 3) ? "\n" : ""
        );
    fputs (
        "] def\n"
        "\n"
        "/BuildGlyph {\n"
        "  exch /CharProcs get exch\n"
        "  2 copy known not {pop /.notdef} if\n"
        "  get exec\n"
        "} bind def\n"
        "\n"
        "/BuildChar {\n"
        "  1 index /Encoding get exch get\n"
        "  1 index /BuildGlyph get exec\n"
        "} bind def\n"
        "\n"
        "/XFontCharPath true def\n"
        "\n"
        "currentdict\n"
        "end\n"
        "\n"
        "/XFont exch definefont pop\n"
        "\n"
        "userdict begin\n"
        "/charpath {\n"
        "  currentfont /XFontCharPath known {\n"
        "    pop\n"
        "    {\n"
        "      { currentfont exch 1 index /BuildChar get exec } stopped {\n"
        "        3 { currentfont /FontMatrix get transform 6 2 roll } repeat\n"
        "        currentpoint\n"
        "        1 index 6 index add 1 index 6 index add moveto\n"
        "        1 index 4 index add 1 index 6 index add lineto\n"
        "        1 index 4 index add 1 index 4 index add lineto\n"
        "        1 index 6 index add 1 index 4 index add lineto\n"
        "        closepath\n"
        "        moveto\n"
        "        pop pop pop pop\n"
        "        rmoveto\n"
        "      } if\n"
        "    } forall\n"
        "  } {\n"
        "    systemdict /charpath get exec\n"
        "  } ifelse\n"
        "} bind def\n"
        "end\n",
        stdout
    );

    XFreeFont (display, font_struct);

    return 0;
}

void remove_entry (int i)
{
    if (names [i]
     && (Encoding == LATIN || Encoding == SYMBOL)
     && (i < 127 || i > 160)       /* don't warn for rarely used symbols */
    )
        fprintf (stderr, "WARNING: Symbol #%i not found: /%s\n", i, names [i]);
    names [i] = NULL;
    if (i == 32 && Encoding == LATIN)
        names [160] = NULL;
}

unsigned offset (int i)
{
    return (
        (FontPage - font_struct->min_byte1)
          * (font_struct->max_char_or_byte2 - font_struct->min_char_or_byte2 + 1)
        - font_struct->min_char_or_byte2
        + i
    );
}

char invert (char c)
{
    switch (c) {
        case '0': return '0';
        case '1': return '8';
        case '2': return '4';
        case '3': return 'c';
        case '4': return '2';
        case '5': return 'a';
        case '6': return '6';
        case '7': return 'e';
        case '8': return '1';
        case '9': return '9';
        case 'a':
        case 'A': return '5';
        case 'b':
        case 'B': return 'd';
        case 'c':
        case 'C': return '3';
        case 'd':
        case 'D': return 'b';
        case 'e':
        case 'E': return '7';
        case 'f':
        case 'F': return 'f';
    }
    return '0';
}

Display *ConnectToServer (
    char display_name [],
    int *screen,
    Window *rootwindow
)
{
    Display
        *display = XOpenDisplay (display_name);

    if (! display)
        errit (
            "Cannot connect to X server [%s]",
            XDisplayName (display_name)
        );

    *screen = DefaultScreen (display);

    *rootwindow = RootWindow (display, *screen);

    return display;
}

void *s_malloc (size_t size)
{
    void
	*p;

    p = malloc (size);
    if (! p) {
        free (no_mem_buffer);
	errit (no_memory);
    }
    return p;
}

char *s_strdup (char const *s)
{
    char
	*s1;

    if (s) {
        s1 = (char *) s_malloc (strlen (s) + 1);
	strcpy (s1, s);
    } else {
        s1 = (char *) s_malloc (1);
	s1 [0] = '\0';
    }
    return s1;
}

void get_programname (char const *argv0)
{
    char
        *p;
    p = strrchr (argv0, '/');
    if (p)
        programname = strdup (p + 1);
    else
        programname = strdup (argv0);
}

void errit (char const *format, ...)
{
    va_list
        list;

    fprintf (stderr, "\nError %s: ", programname);

    va_start (list, format);
    vfprintf (stderr, format, list);

    fprintf (stderr, "\n\n");

    exit (1);
}

void syntax ()
{
    printf (
        "\n"
        "Conversion of 8-bit or 16-bit XFont to PostScript Type 3 Font\n"
        "\n"
        "Usage: %s [-l|-s] 8_bit_fontname > font.ps\n"
        "   or: %s fontpage 16_bit_fontname > font.ps\n"
        "\n"
        "  -l: Use ISOLatin1Encoding\n"
        "  -s: Use SymbolEncoding\n"
        "  fontpage: Page number of 16 bit font\n"
        "\n"
        "Example:\n"
        "  %s '*-helvetica-medium-r-*-14*' > font.ps\n"
        "\n"
        "Usage in PostScript:\n"
        "  (font.ps) run\n"
        "  /XFont findfont setfont\n"
        "Don't use the scalefont, makefont or selectfont operators\n"
        "\n"
        "(c) P. Kleiweg 1997\n"
        "\n",
        programname,
        programname,
        programname
    );
    exit (1);
}

