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