1 /* 2 * download - host resident font downloader 3 * 4 * Prepends host resident fonts to PostScript input files. The program assumes 5 * the input files are part of a single PostScript job and that requested fonts 6 * can be downloaded at the start of each input file. Downloaded fonts are the 7 * ones named in a %%DocumentFonts: comment and listed in a special map table. 8 * Map table pathnames (supplied using the -m option) that begin with a / are 9 * taken as is. Otherwise the final pathname is built using *hostfontdir (-H 10 * option), *mapname (-m option), and *suffix. 11 * 12 * The map table consists of fontname-filename pairs, separated by white space. 13 * Comments are introduced by % (as in PostScript) and extend to the end of the 14 * current line. The only fonts that can be downloaded are the ones listed in 15 * the active map table that point the program to a readable Unix file. A request 16 * for an unlisted font or inaccessible file is ignored. All font requests are 17 * ignored if the map table can't be read. In that case the program simply copies 18 * the input files to stdout. 19 * 20 * An example (but not one to follow) of what can be in a map table is, 21 * 22 * % 23 * % Map requests for Bookman-Light to file *hostfontdir/KR 24 * % 25 * 26 * Bookman-Light KR % Keeping everything (including the map 27 * % table) in *hostfontdir seems like the 28 * % cleanest approach. 29 * 30 * % 31 * % Map Palatino-Roman to file *hostfontdir/palatino/Roman 32 * % 33 * Palatino-Roman palatino/Roman 34 * 35 * % Map ZapfDingbats to file /usr/lib/host/dingbats 36 * 37 * ZapfDingbats /usr/lib/host/dingbats 38 * 39 * Once again, file names that begin with a / are taken as is. All others have 40 * *hostfontdir/ prepended to the file string associated with a particular font. 41 * 42 * Map table can be associated with a printer model (e.g. a LaserWriter), a 43 * printer destination, or whatever - the choice is up to an administrator. 44 * By destination may be best if your spooler is running several private 45 * printers. Host resident fonts are usually purchased under a license that 46 * restricts their use to a limited number of printers. A font licensed for 47 * a single printer should only be used on that printer. 48 * 49 * Was written quickly, so there's much room for improvement. Undoubtedly should 50 * be a more general program (e.g. scan for other comments). 51 */ 52 53 #define _BSD_EXTENSION 54 55 #include <stdio.h> 56 #include <stdlib.h> 57 #include <unistd.h> 58 #include <fcntl.h> 59 #include <string.h> 60 #include <signal.h> 61 #include <sys/types.h> 62 #include <sys/stat.h> 63 64 #include "comments.h" /* PostScript file structuring comments */ 65 #include "gen.h" /* general purpose definitions */ 66 #include "path.h" /* for temporary directory */ 67 #include "ext.h" /* external variable declarations */ 68 #include "download.h" /* a few special definitions */ 69 70 char *temp_dir = TEMPDIR; /* temp directory - for copying stdin */ 71 char *hostfontdir = HOSTDIR; /* host resident directory */ 72 char *mapname = "map"; /* map table - usually in *hostfontdir */ 73 char *suffix = ""; /* appended to the map table pathname */ 74 Map *map = NULL; /* device font map table */ 75 char *stringspace = NULL; /* for storing font and file strings */ 76 int next = 0; /* next free slot in map[] */ 77 78 char *residentfonts = NULL; /* list of printer resident fonts */ 79 char *printer = NULL; /* printer name - only for Unix 4.0 lp */ 80 81 char buf[2048]; /* input file line buffer */ 82 char *comment = DOCUMENTFONTS; /* look for this comment */ 83 int atend = FALSE; /* TRUE only if a comment says so */ 84 85 FILE *fp_in = stdin; /* next input file */ 86 FILE *fp_temp = NULL; /* for copying stdin */ 87 88 void arguments(void); 89 void copyfonts(char *); 90 void copyinput(void); 91 void done(void); 92 void download(void); 93 void init_signals(void); 94 void options(void); 95 void readmap(void); 96 void readresident(void); 97 98 main(agc, agv) 99 int agc; 100 char *agv[]; 101 { 102 /* 103 * 104 * Host resident font downloader. The input files are assumed to be part of a 105 * single PostScript job. 106 * 107 */ 108 109 argc = agc; /* other routines may want them */ 110 argv = agv; 111 112 prog_name = argv[0]; /* just for error messages */ 113 114 init_signals(); /* sets up interrupt handling */ 115 options(); /* first get command line options */ 116 readmap(); /* read the font map table */ 117 readresident(); /* and the optional resident font list */ 118 arguments(); /* then process non-option arguments */ 119 done(); /* and clean things up */ 120 exit(x_stat); /* not much could be wrong */ 121 return 0; 122 } 123 124 void 125 init_signals(void) 126 { 127 /* 128 * Makes sure we handle interrupts properly. 129 */ 130 131 if ( signal(SIGINT, interrupt) == SIG_IGN ) { 132 signal(SIGINT, SIG_IGN); 133 signal(SIGQUIT, SIG_IGN); 134 signal(SIGHUP, SIG_IGN); 135 } else { 136 signal(SIGHUP, interrupt); 137 signal(SIGQUIT, interrupt); 138 } 139 140 signal(SIGTERM, interrupt); 141 } 142 143 void 144 options(void) 145 { 146 int ch; /* return value from getopt() */ 147 char *optnames = "c:fm:p:r:H:T:DI"; 148 149 extern char *optarg; /* used by getopt() */ 150 extern int optind; 151 152 /* 153 * 154 * Reads and processes the command line options. 155 * 156 */ 157 158 while ( (ch = getopt(argc, argv, optnames)) != EOF ) { 159 switch ( ch ) { 160 case 'c': /* look for this comment */ 161 comment = optarg; 162 break; 163 164 case 'f': /* force a complete input file scan */ 165 atend = TRUE; 166 break; 167 168 case 'm': /* printer map table name */ 169 mapname = optarg; 170 break; 171 172 case 'p': /* printer name - for Unix 4.0 lp */ 173 printer = optarg; 174 break; 175 176 case 'r': /* resident font list */ 177 residentfonts = optarg; 178 break; 179 180 case 'H': /* host resident font directory */ 181 hostfontdir = optarg; 182 break; 183 184 case 'T': /* temporary file directory */ 185 temp_dir = optarg; 186 break; 187 188 case 'D': /* debug flag */ 189 debug = ON; 190 break; 191 192 case 'I': /* ignore FATAL errors */ 193 ignore = ON; 194 break; 195 196 case '?': /* don't understand the option */ 197 error(FATAL, ""); 198 break; 199 200 default: /* don't know what to do for ch */ 201 error(FATAL, "missing case for option %c\n", ch); 202 break; 203 } /* End switch */ 204 } /* End while */ 205 206 argc -= optind; /* get ready for non-option args */ 207 argv += optind; 208 } 209 210 void 211 readmap(void) 212 { 213 char *path; 214 char *ptr; 215 int fd; 216 struct stat sbuf; 217 218 /* 219 * Initializes the map table by reading an ASCII mapping file. If mapname begins 220 * with a / it's the map table. Otherwise hostfontdir, mapname, and suffix are 221 * combined to build the final pathname. If we can open the file we read it all 222 * into memory, erase comments, and separate the font and file name pairs. When 223 * we leave next points to the next free slot in the map[] array. If it's zero 224 * nothing was in the file or we couldn't open it. 225 */ 226 227 if ( hostfontdir == NULL || mapname == NULL ) 228 return; 229 230 if ( *mapname != '/' ) { 231 if ( (path = malloc(strlen(hostfontdir) + strlen(mapname) + 232 strlen(suffix) + 2)) == NULL ) 233 error(FATAL, "no memory"); 234 sprintf(path, "%s/%s%s", hostfontdir, mapname, suffix); 235 } else path = mapname; 236 237 if ( (fd = open(path, 0)) != -1 ) { 238 if ( fstat(fd, &sbuf) == -1 ) 239 error(FATAL, "can't fstat %s", path); 240 if ( (stringspace = malloc(sbuf.st_size + 2)) == NULL ) 241 error(FATAL, "no memory for %s (%d bytes)", path, sbuf.st_size + 2); 242 if ( read(fd, stringspace, sbuf.st_size) == -1 ) 243 error(FATAL, "can't read %s", path); 244 close(fd); 245 246 stringspace[sbuf.st_size] = '\n'; /* just to be safe */ 247 stringspace[sbuf.st_size+1] = '\0'; 248 for ( ptr = stringspace; *ptr != '\0'; ptr++ ) /* erase comments */ 249 if ( *ptr == '%' ) 250 for ( ; *ptr != '\n' ; ptr++ ) 251 *ptr = ' '; 252 253 for ( ptr = stringspace; ; next++ ) { 254 if ( (next % 50) == 0 ) 255 map = allocate(map, next+50); 256 map[next].downloaded = FALSE; 257 map[next].font = strtok(ptr, " \t\n"); 258 map[next].file = strtok(ptr = NULL, " \t\n"); 259 if ( map[next].font == NULL ) 260 break; 261 if ( map[next].file == NULL ) 262 error(FATAL, "map table format error - check %s", path); 263 } 264 } 265 } 266 267 void 268 readresident(void) 269 { 270 FILE *fp; 271 char *path; 272 int ch; 273 int n; 274 275 /* 276 * Reads a file that lists the resident fonts for a particular printer and marks 277 * each font as already downloaded. Nothing's done if the file can't be read or 278 * there's no mapping file. Comments, as in the map file, begin with a % and 279 * extend to the end of the line. Added for Unix 4.0 lp. 280 */ 281 282 if ( next == 0 || (printer == NULL && residentfonts == NULL) ) 283 return; 284 285 if ( printer != NULL ) { /* use Unix 4.0 lp pathnames */ 286 sprintf(buf, "%s/printers/%s", HOSTDIR, printer); 287 path = buf; 288 } else path = residentfonts; 289 290 if ( (fp = fopen(path, "r")) != NULL ) { 291 while ( fscanf(fp, "%s", buf) != EOF ) 292 if ( buf[0] == '%' ) 293 while ( (ch = getc(fp)) != EOF && ch != '\n' ) ; 294 else if ( (n = lookup(buf)) < next ) 295 map[n].downloaded = TRUE; 296 fclose(fp); 297 } 298 } 299 300 void 301 arguments(void) 302 { 303 /* 304 * 305 * Makes sure all the non-option command line arguments are processed. If we get 306 * here and there aren't any arguments left, or if '-' is one of the input files 307 * we'll translate stdin. Assumes input files are part of a single PostScript 308 * job and fonts can be downloaded at the start of each file. 309 * 310 */ 311 312 if ( argc < 1 ) 313 download(); 314 else { 315 while ( argc > 0 ) { 316 fp_temp = NULL; 317 if ( strcmp(*argv, "-") == 0 ) 318 fp_in = stdin; 319 else if ( (fp_in = fopen(*argv, "r")) == NULL ) 320 error(FATAL, "can't open %s", *argv); 321 download(); 322 if ( fp_in != stdin ) 323 fclose(fp_in); 324 if ( fp_temp != NULL ) 325 fclose(fp_temp); 326 argc--; 327 argv++; 328 } 329 } 330 } 331 332 void 333 done(void) 334 { 335 /* 336 * Clean things up before we quit. 337 */ 338 if ( temp_file != NULL ) 339 unlink(temp_file); 340 } 341 342 void 343 download(void) 344 { 345 int infontlist = FALSE; 346 347 /* 348 * 349 * If next is zero the map table is empty and all we do is copy the input file 350 * to stdout. Otherwise we read the input file looking for %%DocumentFonts: or 351 * continuation comments, add any accessible fonts to the output file, and then 352 * append the input file. When reading stdin we append lines to fp_temp and 353 * recover them when we're ready to copy the input file. fp_temp will often 354 * only contain part of stdin - if there's no %%DocumentFonts: (atend) comment 355 * we stop reading fp_in after the header. 356 * 357 */ 358 359 if ( next > 0 ) { 360 if ( fp_in == stdin ) { 361 if ( (temp_file = tempnam(temp_dir, "post")) == NULL ) 362 error(FATAL, "can't generate temp file name"); 363 if ( (fp_temp = fopen(temp_file, "w+r")) == NULL ) 364 error(FATAL, "can't open %s", temp_file); 365 unlink(temp_file); 366 } /* End if */ 367 368 while ( fgets(buf, sizeof(buf), fp_in) != NULL ) { 369 if ( fp_temp != NULL ) 370 fprintf(fp_temp, "%s", buf); 371 if ( buf[0] != '%' || buf[1] != '%' ) { 372 if ( (buf[0] != '%' || buf[1] != '!') && atend == FALSE ) 373 break; 374 infontlist = FALSE; 375 } else if ( strncmp(buf, comment, strlen(comment)) == 0 ) { 376 copyfonts(buf); 377 infontlist = TRUE; 378 } else if ( buf[2] == '+' && infontlist == TRUE ) 379 copyfonts(buf); 380 else infontlist = FALSE; 381 } 382 } 383 copyinput(); 384 } 385 386 void 387 copyfonts(list) 388 char *list; 389 { 390 char *font; 391 char *path; 392 int n; 393 394 /* 395 * list points to a %%DocumentFonts: or continuation comment. What follows the 396 * the keyword will be a list of fonts separated by white space (or (atend)). 397 * Look for each font in the map table and if it's found copy the font file to 398 * stdout (once only). 399 */ 400 401 strtok(list, " \n"); /* skip to the font list */ 402 403 while ( (font = strtok(NULL, " \t\n")) != NULL ) { 404 if ( strcmp(font, ATEND) == 0 ) { 405 atend = TRUE; 406 break; 407 } 408 if ( (n = lookup(font)) < next ) { 409 if ( *map[n].file != '/' ) { 410 if ( (path = malloc(strlen(hostfontdir)+strlen(map[n].file)+2)) == NULL ) 411 error(FATAL, "no memory"); 412 sprintf(path, "%s/%s", hostfontdir, map[n].file); 413 cat(path); 414 free(path); 415 } else cat(map[n].file); 416 map[n].downloaded = TRUE; 417 } 418 } 419 } 420 421 void 422 copyinput(void) 423 { 424 /* 425 * 426 * Copies the input file to stdout. If fp_temp isn't NULL seek to the start and 427 * add it to the output file - it's a partial (or complete) copy of stdin made 428 * by download(). Then copy fp_in, but only seek to the start if it's not stdin. 429 * 430 */ 431 432 if ( fp_temp != NULL ) { 433 fseek(fp_temp, 0L, 0); 434 while ( fgets(buf, sizeof(buf), fp_temp) != NULL ) 435 printf("%s", buf); 436 } /* End if */ 437 438 if ( fp_in != stdin ) 439 fseek(fp_in, 0L, 0); 440 441 while ( fgets(buf, sizeof(buf), fp_in) != NULL ) 442 printf("%s", buf); 443 } 444 445 lookup(font) 446 char *font; 447 { 448 int i; 449 450 /* 451 * 452 * Looks for *font in the map table. Return the map table index if found and 453 * not yet downloaded - otherwise return next. 454 * 455 */ 456 457 for ( i = 0; i < next; i++ ) 458 if ( strcmp(font, map[i].font) == 0 ) { 459 if ( map[i].downloaded == TRUE ) 460 i = next; 461 break; 462 } /* End if */ 463 464 return(i); 465 466 } /* End of lookup */ 467 468 /* 469 * Allocates space for num Map elements. Calls malloc() if ptr is NULL and 470 * realloc() otherwise. 471 */ 472 Map * 473 allocate(Map *ptr, int num) 474 { 475 if (ptr == NULL) 476 ptr = (Map *)malloc(num * sizeof(Map)); 477 else 478 ptr = (Map *)realloc(ptr, num * sizeof(Map)); 479 if (ptr == NULL) 480 error(FATAL, "no map memory"); 481 return ptr; 482 } 483