xref: /plan9/sys/src/cmd/postscript/download/download.c (revision 14f51593fd82e19ba95969a8c07ff71131015979)
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 
main(agc,agv)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
init_signals(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
options(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
readmap(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
readresident(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
arguments(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
done(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
download(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
copyfonts(list)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
copyinput(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 
lookup(font)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 *
allocate(Map * ptr,int num)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