/* * * download - host resident font downloader * * Prepends host resident fonts to PostScript input files. The program assumes * the input files are part of a single PostScript job and that requested fonts * can be downloaded at the start of each input file. Downloaded fonts are the * ones named in a %%DocumentFonts: comment and listed in a special map table. * Map table pathnames (supplied using the -m option) that begin with a / are * taken as is. Otherwise the final pathname is built using *hostfontdir (-H * option), *mapname (-m option), and *suffix. * * The map table consists of fontname-filename pairs, separated by white space. * Comments are introduced by % (as in PostScript) and extend to the end of the * current line. The only fonts that can be downloaded are the ones listed in * the active map table that point the program to a readable Unix file. A request * for an unlisted font or inaccessible file is ignored. All font requests are * ignored if the map table can't be read. In that case the program simply copies * the input files to stdout. * * An example (but not one to follow) of what can be in a map table is, * * % * % Map requests for Bookman-Light to file *hostfontdir/KR * % * * Bookman-Light KR % Keeping everything (including the map * % table) in *hostfontdir seems like the * % cleanest approach. * * % * % Map Palatino-Roman to file *hostfontdir/palatino/Roman * % * Palatino-Roman palatino/Roman * * % Map ZapfDingbats to file /usr/lib/host/dingbats * * ZapfDingbats /usr/lib/host/dingbats * * Once again, file names that begin with a / are taken as is. All others have * *hostfontdir/ prepended to the file string associated with a particular font. * * Map table can be associated with a printer model (e.g. a LaserWriter), a * printer destination, or whatever - the choice is up to an administrator. * By destination may be best if your spooler is running several private * printers. Host resident fonts are usually purchased under a license that * restricts their use to a limited number of printers. A font licensed for * a single printer should only be used on that printer. * * Was written quickly, so there's much room for improvement. Undoubtedly should * be a more general program (e.g. scan for other comments). * */ #include #include #include #include #include #include #include "comments.h" /* PostScript file structuring comments */ #include "gen.h" /* general purpose definitions */ #include "path.h" /* for temporary directory */ #include "ext.h" /* external variable declarations */ #include "download.h" /* a few special definitions */ char *temp_dir = TEMPDIR; /* temp directory - for copying stdin */ char *hostfontdir = HOSTDIR; /* host resident directory */ char *mapname = "map"; /* map table - usually in *hostfontdir */ char *suffix = ""; /* appended to the map table pathname */ Map *map = NULL; /* device font map table */ char *stringspace = NULL; /* for storing font and file strings */ int next = 0; /* next free slot in map[] */ char *residentfonts = NULL; /* list of printer resident fonts */ char *printer = NULL; /* printer name - only for Unix 4.0 lp */ char buf[2048]; /* input file line buffer */ char *comment = DOCUMENTFONTS; /* look for this comment */ int atend = FALSE; /* TRUE only if a comment says so */ FILE *fp_in = stdin; /* next input file */ FILE *fp_temp = NULL; /* for copying stdin */ void download(void); void copyinput(void); extern getcallerpc(void*); void init_signals(void) { if ( signal(SIGINT, interrupt) == SIG_IGN ) { signal(SIGINT, SIG_IGN); signal(SIGQUIT, SIG_IGN); signal(SIGHUP, SIG_IGN); } else { signal(SIGHUP, interrupt); signal(SIGQUIT, interrupt); } signal(SIGTERM, interrupt); } void options(void) { char *optnames = "c:fm:p:r:H:T:DI"; int ch; extern char *optarg; extern int optind; /* * * Reads and processes the command line options. * */ while((ch = getopt(argc, argv, optnames))!= EOF){ switch(ch){ case 'c': /* look for this comment */ comment = optarg; break; case 'f': /* force a complete input file scan */ atend = TRUE; break; case 'm': /* printer map table name */ mapname = optarg; break; case 'p': /* printer name - for Unix 4.0 lp */ printer = optarg; break; case 'r': /* resident font list */ residentfonts = optarg; break; case 'H': /* host resident font directory */ hostfontdir = optarg; break; case 'T': /* temporary file directory */ temp_dir = optarg; break; case 'D': /* debug flag */ debug = ON; break; case 'I': /* ignore FATAL errors */ ignore = ON; break; case '?': /* don't understand the option */ error(FATAL, ""); break; default: /* don't know what to do for ch */ error(FATAL, "missing case for option %c\n", ch); break; } } argc -= optind; /* get ready for non-option args */ argv += optind; } void* emalloc(long n) { void *v; v = (void*)malloc(n); if(v == 0){ // sysfatal("malloc: %r from %p %ld\n", getcallerpc(&n), n); fprintf(stderr, "malloc: %r for %ld bytes\n", n); abort(); } // setmalloctag(v, getcallerpc(&n)); return v; } void readmap(void) { char *path, *ptr; int fd; struct stat sbuf; /* * Initializes the map table by reading an ASCII mapping file. If mapname begins * with a / it's the map table. Otherwise hostfontdir, mapname, and suffix are * combined to build the final pathname. If we can open the file we read it all * into memory, erase comments, and separate the font and file name pairs. When * we leave next points to the next free slot in the map[] array. If it's zero * nothing was in the file or we couldn't open it. * */ if(hostfontdir == NULL || mapname == NULL) return; if(*mapname != '/'){ path = emalloc(strlen(hostfontdir) + strlen(mapname) + strlen(suffix) + 2); sprintf(path, "%s/%s%s", hostfontdir, mapname, suffix); } else path = mapname; if((fd = open(path, 0))!= -1){ if(fstat(fd, &sbuf)== -1) error(FATAL, "can't fstat %s", path); stringspace = emalloc(sbuf.st_size + 2); if(read(fd, stringspace, sbuf.st_size)== -1) error(FATAL, "can't read %s", path); close(fd); stringspace[sbuf.st_size] = '\n'; /* just to be safe */ stringspace[sbuf.st_size+1] = '\0'; for(ptr = stringspace; *ptr != '\0'; ptr++) /* erase comments */ if(*ptr == '%') for(; *ptr != '\n' ; ptr++) *ptr = ' '; for(ptr = stringspace; ; next++){ if((next % 50)== 0) map = allocate(map, next+50); map[next].downloaded = FALSE; map[next].font = strtok(ptr, " \t\n"); map[next].file = strtok(ptr = NULL, " \t\n"); if(map[next].font == NULL) break; if(map[next].file == NULL) error(FATAL, "map table format error - check %s", path); } } } void readresident(void) { char *path; int ch, n; FILE *fp; /* * * Reads a file that lists the resident fonts for a particular printer and marks * each font as already downloaded. Nothing's done if the file can't be read or * there's no mapping file. Comments, as in the map file, begin with a % and * extend to the end of the line. Added for Unix 4.0 lp. * */ if(next == 0 ||(printer == NULL && residentfonts == NULL)) return; if(printer != NULL){ /* use Unix 4.0 lp pathnames */ sprintf(buf, "%s/printers/%s", HOSTDIR, printer); path = buf; } else path = residentfonts; if((fp = fopen(path, "r"))!= NULL){ while(fscanf(fp, "%s", buf)!= EOF) if(buf[0] == '%') while((ch = getc(fp))!= EOF && ch != '\n'); else if((n = lookup(buf))< next) map[n].downloaded = TRUE; fclose(fp); } } void arguments(void) { /* * * Makes sure all the non-option command line arguments are processed. If we get * here and there aren't any arguments left, or if '-' is one of the input files * we'll translate stdin. Assumes input files are part of a single PostScript * job and fonts can be downloaded at the start of each file. * */ if(argc < 1) download(); else { while(argc > 0){ fp_temp = NULL; if(strcmp(*argv, "-")== 0) fp_in = stdin; else if((fp_in = fopen(*argv, "r"))== NULL) error(FATAL, "can't open %s", *argv); download(); if(fp_in != stdin) fclose(fp_in); if(fp_temp != NULL) fclose(fp_temp); argc--; argv++; } } } void done(void) { if(temp_file != NULL) unlink(temp_file); } void download(void) { int infontlist = FALSE; /* * * If next is zero the map table is empty and all we do is copy the input file * to stdout. Otherwise we read the input file looking for %%DocumentFonts: or * continuation comments, add any accessible fonts to the output file, and then * append the input file. When reading stdin we append lines to fp_temp and * recover them when we're ready to copy the input file. fp_temp will often * only contain part of stdin - if there's no %%DocumentFonts:(atend)comment * we stop reading fp_in after the header. * */ if(next > 0){ if(fp_in == stdin){ if((temp_file = tempnam(temp_dir, "post"))== NULL) error(FATAL, "can't generate temp file name"); if((fp_temp = fopen(temp_file, "w+r"))== NULL) error(FATAL, "can't open %s", temp_file); unlink(temp_file); } while(fgets(buf, sizeof(buf), fp_in)!= NULL){ if(fp_temp != NULL) fprintf(fp_temp, "%s", buf); if(buf[0] != '%' || buf[1] != '%'){ if((buf[0] != '%' || buf[1] != '!')&& atend == FALSE) break; infontlist = FALSE; } else if(strncmp(buf, comment, strlen(comment))== 0){ copyfonts(buf); infontlist = TRUE; } else if(buf[2] == '+' && infontlist == TRUE) copyfonts(buf); else infontlist = FALSE; } } copyinput(); } int copyfonts(char *list) { char *font; char *path; int n; /* * * list points to a %%DocumentFonts: or continuation comment. What follows the * the keyword will be a list of fonts separated by white space(or(atend)). * Look for each font in the map table and if it's found copy the font file to * stdout(once only). * */ strtok(list, " \n"); /* skip to the font list */ while((font = strtok(NULL, " \t\n"))!= NULL){ if(strcmp(font, ATEND)== 0){ atend = TRUE; break; } if((n = lookup(font))< next){ if(*map[n].file != '/'){ path = emalloc(strlen(hostfontdir)+strlen(map[n].file)+2); sprintf(path, "%s/%s", hostfontdir, map[n].file); cat(path); free(path); } else cat(map[n].file); map[n].downloaded = TRUE; } } } void copyinput(void) { /* * * Copies the input file to stdout. If fp_temp isn't NULL seek to the start and * add it to the output file - it's a partial (or complete) copy of stdin made * by download(). Then copy fp_in, but only seek to the start if it's not stdin. * */ if(fp_temp != NULL){ fseek(fp_temp, 0L, 0); while(fgets(buf, sizeof(buf), fp_temp)!= NULL) printf("%s", buf); } if(fp_in != stdin) fseek(fp_in, 0L, 0); while(fgets(buf, sizeof(buf), fp_in)!= NULL) printf("%s", buf); } int lookup(char *font) { int i; for(i = 0; i < next; i++) if(strcmp(font, map[i].font)== 0){ if(map[i].downloaded == TRUE) i = next; break; } return i; } Map* allocate(Map *ptr, int num) { ptr = (Map *)realloc(ptr, num * sizeof(Map)); if ( ptr == NULL ) error(FATAL, "no map memory"); return ptr; } int main(int agc, char **agv) { argc = agc; /* other routines may want them */ argv = agv; prog_name = argv[0]; /* just for error messages */ init_signals(); /* sets up interrupt handling */ options(); /* first get command line options */ readmap(); /* read the font map table */ readresident(); /* and the optional resident font list */ arguments(); /* then process non-option arguments */ done(); /* and clean things up */ exit(x_stat); /* not much could be wrong */ }