/* t1disasm ** ** This program `disassembles' Adobe Type-1 font programs in either PFB or PFA ** format. It produces a human readable/editable pseudo-PostScript file by ** performing eexec and charstring decryption as specified in the `Adobe Type 1 ** Font Format' version 1.1 (the `black book'). There is a companion program, ** t1asm, which `assembles' such a pseudo-PostScript file into either PFB or ** PFA 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. ** ** author: I. Lee Hetherington (ilh@lcs.mit.edu) */ #ifndef lint static char sccsid[] = "@(#) t1disasm.c 1.2 10:12:48 5/22/92"; static char copyright[] = "@(#) Copyright (c) 1992 by I. Lee Hetherington, all rights reserved."; #endif /* Note: this is ANSI C. */ #include #include #include #include #ifdef MSDOS #define RB "rb" #else #define RB "r" #endif #define BANNER "This is t1disasm 1.2.\n" #define LINESIZE 256 #define cgetc() cdecrypt(egetc()) typedef unsigned char byte; static FILE *ifp = stdin; static FILE *ofp = stdout; static char line[LINESIZE]; static int start_charstring = 0; static int lenIV = 4; /* decryption stuff */ static unsigned short er, cr; static unsigned short c1 = 52845, c2 = 22719; /* This function looks for `currentfile eexec' string and returns 1 once found. If c == 0, then simply check the status. */ static int eexec_scanner(char c) { static char *key = "currentfile eexec\n"; static char *p = 0; if (!p) p = key; if (c && *p) { if (c == *p) ++p; else p = key; } return *p == '\0'; } /* This function returns the value of a single 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 returns a single character at a time from a PFA or PFB file. This stream is mixed ASCII and binary bytes. For PFB files, the section headers are removed, and \r is replaced by \n in ASCII sections. For PFA files, the hexdecimal data is turned into binary bytes. */ static int bgetc() { static int first_byte = 1; static int is_pfa = 0; static int is_pfb = 0; static int pfb_remaining = 0; int c, val; /* is_pfa == 1 means PFA initial ASCII section is_pfa == 2 means PFA hexadecimal section is_pfb == 1 means PFB ASCII section is_pfB == 2 means PFB binary section */ c = fgetc(ifp); if (c == EOF) return EOF; if (first_byte) { /* Determine if this is a PFA or PFB file by looking at first byte. */ if (c == 0x80) { is_pfb = 1; is_pfa = 0; } else { is_pfb = 0; is_pfa = 1; } first_byte = 0; } if (is_pfb) { /* PFB */ if (pfb_remaining == 0) { /* beginning of block---we know c == 0x80 at this point */ switch (fgetc(ifp)) { case 1: is_pfb = 1; break; case 2: is_pfb = 2; break; case 3: return EOF; default: fprintf(stderr, "error: is this really a PFB file?\n"); exit(1); } /* get block length */ pfb_remaining = fgetc(ifp) & 0xff; pfb_remaining |= (fgetc(ifp) & 0xff) << 8; pfb_remaining |= (fgetc(ifp) & 0xff) << 16; pfb_remaining |= (fgetc(ifp) & 0xff) << 24; /* get character */ c = fgetc(ifp); if (c == EOF) return EOF; } --pfb_remaining; /* in ASCII section change return to newline */ if (is_pfb == 1 && c == '\r') c = '\n'; (void)eexec_scanner(c); return c; } else { /* PFA */ if (is_pfa == 1) { /* in initial ASCII */ if (eexec_scanner(c)) is_pfa = 2; return c; } else { /* in hexadecimal */ while (isspace(c)) c = fgetc(ifp); val = hexval((char)c) << 4; val |= hexval((char)(c = fgetc(ifp))); if (c == EOF) return EOF; return val; } } } /* Two separate decryption functions because eexec and charstring decryption must proceed in parallel. */ static byte edecrypt(byte cipher) { byte plain; plain = (cipher ^ (er >> 8)); er = (cipher + er) * c1 + c2; return plain; } static byte cdecrypt(byte cipher) { byte plain; plain = (cipher ^ (cr >> 8)); cr = (cipher + cr) * c1 + c2; return plain; } /* This function returns 1 the first time the eexec_scanner returns 1. */ static int immediate_eexec() { static int reported = 0; if (!reported && eexec_scanner(0)) { reported = 1; return 1; } else { return 0; } } /* This function returns a single byte at a time through (possible) eexec decryption. When immediate_eexec returns 1 it fires up the eexec decryption machinery. */ static int egetc() { static int in_eexec = 0; int c; if ((c = bgetc()) == EOF) return EOF; if (!in_eexec) { if (immediate_eexec()) { /* start eexec decryption */ in_eexec = 1; er = 55665; /* toss out four random bytes */ (void) edecrypt(bgetc()); (void) edecrypt(bgetc()); (void) edecrypt(bgetc()); (void) edecrypt(bgetc()); } return c; } else { return edecrypt(c); } } /* This function returns a line of eexec decrypted characters. A line is terminated by length (including terminating null) greater than LINESIZE, a newline \n, or one of the special charstring start sequences ` -| ' or ` RD '. The line, including the terminating newline or charstring start sequence is put into line[]. If terminated by a charstring start sequence, the flag start_charstring is set to 1. */ static void egetline() { int c; char *p = line; start_charstring = 0; while (p < line + LINESIZE) { c = egetc(); if (c == EOF) break; *p++ = (char) c; if (p >= line + 4 && (strncmp(p - 4, " -| ", 4) == 0 || strncmp(p - 4, " RD ", 4) == 0)) { p -= 4; start_charstring = 1; break; } if (c == '\r') { /* map \r to \n */ p[-1] = '\n'; break; } if (c == '\n') break; } *p = '\0'; } /* If the line contains an entry of the form `/lenIV ' then set the global lenIV to . This indicates the number of random bytes at the beginning of each charstring. */ static void set_lenIV() { char *p = strstr(line, "/lenIV "); if (p && isdigit(p[7])) { lenIV = atoi(p + 7); } } /* Subroutine to output strings. */ static void output(char *string) { fprintf(ofp, "%s", string); } /* Subroutine to neatly format output of charstring tokens. If token = "\n", then a newline is output. If at start of line (start == 1), prefix token with tab, otherwise a space. */ static void output_token(char *token) { static int start = 1; if (strcmp(token, "\n") == 0) { fprintf(ofp, "\n"); start = 1; } else { fprintf(ofp, "%s%s", start ? "\t" : " ", token); start = 0; } } /* Subroutine to decrypt and ASCII-ify tokens in charstring data. First, the length (in bytes) of the charstring is determined from line[]. Then the charstring decryption machinery is fired up, skipping the first lenIV bytes. Finally, the decrypted tokens are expanded into human-readable form. */ static void do_charstring() { int l = strlen(line); char *p = line + l - 1; int cs_len; int i; int b, val; char buf[20]; while (p >= line && *p != ' ' && *p != '\t') --p; cs_len = atoi(p); *p = '\0'; output(line); output(" {\n"); cr = 4330; for (i = 0; i < lenIV; i++, cs_len--) (void) cgetc(); while (cs_len > 0) { --cs_len; b = cgetc(); if (b >= 32) { if (b >= 32 && b <= 246) { val = b - 139; } else if (b >= 247 && b <= 250) { --cs_len; val = (b - 247)*256 + 108 + cgetc(); } else if (b >= 251 && b <= 254) { --cs_len; val = -(b - 251)*256 - 108 - cgetc(); } else if (b == 255) { cs_len -= 4; val = (cgetc() & 0xff) << 24; val |= (cgetc() & 0xff) << 16; val |= (cgetc() & 0xff) << 8; val |= (cgetc() & 0xff) << 0; /* in case an int is larger than four bytes---sign extend */ if (sizeof(int) > 4 && val & (1U << 31)) for (i = 4; i < sizeof(int); i++) val |= 0xff << (i * 8); } sprintf(buf, "%d", val); output_token(buf); } else { switch (b) { case 1: output_token("hstem"); break; case 3: output_token("vstem"); break; case 4: output_token("vmoveto"); break; case 5: output_token("rlineto"); break; case 6: output_token("hlineto"); break; case 7: output_token("vlineto"); break; case 8: output_token("rrcurveto"); break; case 9: output_token("closepath"); break; case 10: output_token("callsubr"); break; case 11: output_token("return"); break; case 13: output_token("hsbw"); break; case 14: output_token("endchar"); break; case 21: output_token("rmoveto"); break; case 22: output_token("hmoveto"); break; case 30: output_token("vhcurveto"); break; case 31: output_token("hvcurveto"); break; case 12: --cs_len; switch (b = cgetc()) { case 0: output_token("dotsection"); break; case 1: output_token("vstem3"); break; case 2: output_token("hstem3"); break; case 6: output_token("seac"); break; case 7: output_token("sbw"); break; case 12: output_token("div"); break; case 16: output_token("callothersubr"); break; case 17: output_token("pop"); break; case 33: output_token("setcurrentpoint"); break; default: sprintf(buf, "UNKNOWN_12_%d", b); output_token(buf); break; } break; default: sprintf(buf, "UNKNOWN_%d", b); output_token(buf); break; } output_token("\n"); } } output("\t}"); } int main(int argc, char **argv) { fprintf(stderr, "%s", BANNER); /* possibly open input & output files */ if (argc >= 2) { ifp = fopen(argv[1], RB); if (!ifp) { fprintf(stderr, "error: cannot open %s for reading\n", argv[1]); exit(1); } } if (argc >= 3) { ofp = fopen(argv[2], "w"); if (!ofp) { fprintf(stderr, "error: cannot open %s for writing\n", argv[2]); exit(1); } } /* main loop---normally done when reach `mark currentfile closefile' on output (rest is garbage). */ for (;;) { egetline(); if (line[0] == '\0') break; set_lenIV(); if (start_charstring) do_charstring(); else output(line); if (strcmp(line, "mark currentfile closefile\n") == 0) break; } fclose(ifp); fclose(ofp); return 0; }