Initial import from FreeBSD RELENG_4:
[dragonfly.git] / contrib / groff / src / devices / grops / psrm.cc
1 // -*- C++ -*-
2 /* Copyright (C) 1989, 1990, 1991, 1992, 2000, 2001, 2002
3    Free Software Foundation, Inc.
4      Written by James Clark (jjc@jclark.com)
5
6 This file is part of groff.
7
8 groff is free software; you can redistribute it and/or modify it under
9 the terms of the GNU General Public License as published by the Free
10 Software Foundation; either version 2, or (at your option) any later
11 version.
12
13 groff is distributed in the hope that it will be useful, but WITHOUT ANY
14 WARRANTY; without even the implied warranty of MERCHANTABILITY or
15 FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
16 for more details.
17
18 You should have received a copy of the GNU General Public License along
19 with groff; see the file COPYING.  If not, write to the Free Software
20 Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */
21
22 #include "driver.h"
23 #include "stringclass.h"
24 #include "cset.h"
25
26 #include "ps.h"
27
28 #ifdef NEED_DECLARATION_PUTENV
29 extern "C" {
30   int putenv(const char *);
31 }
32 #endif /* NEED_DECLARATION_PUTENV */
33
34 #define GROPS_PROLOGUE "prologue"
35
36 static void print_ps_string(const string &s, FILE *outfp);
37
38 cset white_space("\n\r \t");
39 string an_empty_string;
40
41 const char *extension_table[] = {
42   "DPS",
43   "CMYK",
44   "Composite",
45   "FileSystem",
46 };
47
48 const int NEXTENSIONS = sizeof(extension_table)/sizeof(extension_table[0]);
49
50 const char *resource_table[] = {
51   "font",
52   "procset",
53   "file",
54   "encoding",
55   "form",
56   "pattern",
57 };
58
59 const int NRESOURCES = sizeof(resource_table)/sizeof(resource_table[0]);
60
61 static int read_uint_arg(const char **pp, unsigned *res)
62 {
63   while (white_space(**pp))
64     *pp += 1;
65   if (**pp == '\0') {
66     error("missing argument");
67     return 0;
68   }
69   const char *start = *pp;
70   // XXX use strtoul
71   long n = strtol(start, (char **)pp, 10);
72   if (n == 0 && *pp == start) {
73     error("not an integer");
74     return 0;
75   }
76   if (n < 0) {
77     error("argument must not be negative");
78     return 0;
79   }
80   *res = unsigned(n);
81   return 1;
82 }
83
84 struct resource {
85   resource *next;
86   resource_type type;
87   string name;
88   enum { NEEDED = 01, SUPPLIED = 02, FONT_NEEDED = 04, BUSY = 010 };
89   unsigned flags;
90   string version;
91   unsigned revision;
92   char *filename;
93   int rank;
94   resource(resource_type, string &, string & = an_empty_string, unsigned = 0);
95   ~resource();
96   void print_type_and_name(FILE *outfp);
97 };
98
99 resource::resource(resource_type t, string &n, string &v, unsigned r)
100 : next(0), type(t), flags(0), revision(r), filename(0), rank(-1)
101 {
102   name.move(n);
103   version.move(v);
104   if (type == RESOURCE_FILE) {
105     if (name.search('\0') >= 0)
106       error("filename contains a character with code 0");
107     filename = name.extract();
108   }
109 }
110
111 resource::~resource()
112 {
113   a_delete filename;
114 }
115
116 void resource::print_type_and_name(FILE *outfp)
117 {
118   fputs(resource_table[type], outfp);
119   putc(' ', outfp);
120   print_ps_string(name, outfp);
121   if (type == RESOURCE_PROCSET) {
122     putc(' ', outfp);
123     print_ps_string(version, outfp);
124     fprintf(outfp, " %u", revision);
125   }
126 }
127
128 resource_manager::resource_manager()
129 : extensions(0), language_level(0), resource_list(0)
130 {
131   read_download_file();
132   string procset_name("grops");
133   extern const char *version_string;
134   extern const char *revision_string;
135   unsigned revision_uint;
136   if ( !read_uint_arg( &revision_string, &revision_uint) )
137           revision_uint = 0;
138   string procset_version(version_string);
139   procset_resource = lookup_resource(RESOURCE_PROCSET, procset_name,
140                                      procset_version, revision_uint);
141   procset_resource->flags |= resource::SUPPLIED;
142 }
143
144 resource_manager::~resource_manager()
145 {
146   while (resource_list) {
147     resource *tem = resource_list;
148     resource_list = resource_list->next;
149     delete tem;
150   }
151 }
152
153 resource *resource_manager::lookup_resource(resource_type type,
154                                             string &name,
155                                             string &version,
156                                             unsigned revision)
157 {
158   resource *r;
159   for (r = resource_list; r; r = r->next)
160     if (r->type == type
161         && r->name == name
162         && r->version == version
163         && r->revision == revision)
164       return r;
165   r = new resource(type, name, version, revision);
166   r->next = resource_list;
167   resource_list = r;
168   return r;
169 }
170
171 // Just a specialized version of lookup_resource().
172
173 resource *resource_manager::lookup_font(const char *name)
174 {
175   resource *r;
176   for (r = resource_list; r; r = r->next)
177     if (r->type == RESOURCE_FONT
178         && strlen(name) == (size_t)r->name.length()
179         && memcmp(name, r->name.contents(), r->name.length()) == 0)
180       return r;
181   string s(name);
182   r = new resource(RESOURCE_FONT, s);
183   r->next = resource_list;
184   resource_list = r;
185   return r;
186 }
187
188 void resource_manager::need_font(const char *name)
189 {
190   lookup_font(name)->flags |= resource::FONT_NEEDED;
191 }
192
193 typedef resource *Presource;    // Work around g++ bug.
194
195 void resource_manager::document_setup(ps_output &out)
196 {
197   int nranks = 0;
198   resource *r;
199   for (r = resource_list; r; r = r->next)
200     if (r->rank >= nranks)
201       nranks = r->rank + 1;
202   if (nranks > 0) {
203     // Sort resource_list in reverse order of rank.
204     Presource *head = new Presource[nranks + 1];
205     Presource **tail = new Presource *[nranks + 1];
206     int i;
207     for (i = 0; i < nranks + 1; i++) {
208       head[i] = 0;
209       tail[i] = &head[i];
210     }
211     for (r = resource_list; r; r = r->next) {
212       i = r->rank < 0 ? 0 : r->rank + 1;
213       *tail[i] = r;
214       tail[i] = &(*tail[i])->next;
215     }
216     resource_list = 0;
217     for (i = 0; i < nranks + 1; i++)
218       if (head[i]) {
219         *tail[i] = resource_list;
220         resource_list = head[i];
221       }
222     a_delete head;
223     a_delete tail;
224     // check it
225     for (r = resource_list; r; r = r->next)
226       if (r->next)
227         assert(r->rank >= r->next->rank);
228     for (r = resource_list; r; r = r->next)
229       if (r->type == RESOURCE_FONT && r->rank >= 0)
230         supply_resource(r, -1, out.get_file());
231   }
232 }
233
234 void resource_manager::print_resources_comment(unsigned flag, FILE *outfp)
235 {
236   int continued = 0;
237   for (resource *r = resource_list; r; r = r->next)
238     if (r->flags & flag) {
239       if (continued)
240         fputs("%%+ ", outfp);
241       else {
242         fputs(flag == resource::NEEDED
243               ? "%%DocumentNeededResources: "
244               : "%%DocumentSuppliedResources: ",
245               outfp);
246         continued = 1;
247       }
248       r->print_type_and_name(outfp);
249       putc('\n', outfp);
250     }
251 }
252
253 void resource_manager::print_header_comments(ps_output &out)
254 {
255   for (resource *r = resource_list; r; r = r->next)
256     if (r->type == RESOURCE_FONT && (r->flags & resource::FONT_NEEDED))
257       supply_resource(r, 0, 0);
258   print_resources_comment(resource::NEEDED, out.get_file());
259   print_resources_comment(resource::SUPPLIED, out.get_file());
260   print_language_level_comment(out.get_file());
261   print_extensions_comment(out.get_file());
262 }
263
264 void resource_manager::output_prolog(ps_output &out)
265 {
266   FILE *outfp = out.get_file();
267   out.end_line();
268   char *path;
269   if (!getenv("GROPS_PROLOGUE")) {
270     string e = "GROPS_PROLOGUE";
271     e += '=';
272     e += GROPS_PROLOGUE;
273     e += '\0';
274     if (putenv(strsave(e.contents())))
275       fatal("putenv failed");
276   }
277   char *prologue = getenv("GROPS_PROLOGUE");
278   FILE *fp = font::open_file(prologue, &path);
279   if (!fp)
280     fatal("can't find `%1'", prologue);
281   fputs("%%BeginResource: ", outfp);
282   procset_resource->print_type_and_name(outfp);
283   putc('\n', outfp);
284   process_file(-1, fp, path, outfp);
285   fclose(fp);
286   a_delete path;
287   fputs("%%EndResource\n", outfp);
288 }
289
290 void resource_manager::import_file(const char *filename, ps_output &out)
291 {
292   out.end_line();
293   string name(filename);
294   resource *r = lookup_resource(RESOURCE_FILE, name);
295   supply_resource(r, -1, out.get_file(), 1);
296 }
297
298 void resource_manager::supply_resource(resource *r, int rank, FILE *outfp,
299                                        int is_document)
300 {
301   if (r->flags & resource::BUSY) {
302     r->name += '\0';
303     fatal("loop detected in dependency graph for %1 `%2'",
304           resource_table[r->type],
305           r->name.contents());
306   }
307   r->flags |= resource::BUSY;
308   if (rank > r->rank)
309     r->rank = rank;
310   char *path;
311   FILE *fp = 0;
312   if (r->filename != 0) {
313     if (r->type == RESOURCE_FONT) {
314       fp = font::open_file(r->filename, &path);
315       if (!fp) {
316         error("can't find `%1'", r->filename);
317         a_delete r->filename;
318         r->filename = 0;
319       }
320     }
321     else {
322       errno = 0;
323       fp = fopen(r->filename, "r");
324       if (!fp) {
325         error("can't open `%1': %2", r->filename, strerror(errno));
326         a_delete r->filename;
327         r->filename = 0;
328       }
329       else
330         path = r->filename;
331     }
332   }
333   if (fp) {
334     if (outfp) {
335       if (r->type == RESOURCE_FILE && is_document) {
336         fputs("%%BeginDocument: ", outfp);
337         print_ps_string(r->name, outfp);
338         putc('\n', outfp);
339       }
340       else {
341         fputs("%%BeginResource: ", outfp);
342         r->print_type_and_name(outfp);
343         putc('\n', outfp);
344       }
345     }
346     process_file(rank, fp, path, outfp);
347     fclose(fp);
348     if (r->type == RESOURCE_FONT)
349       a_delete path;
350     if (outfp) {
351       if (r->type == RESOURCE_FILE && is_document)
352         fputs("%%EndDocument\n", outfp);
353       else
354         fputs("%%EndResource\n", outfp);
355     }
356     r->flags |= resource::SUPPLIED;
357   }
358   else {
359     if (outfp) {
360       if (r->type == RESOURCE_FILE && is_document) {
361         fputs("%%IncludeDocument: ", outfp);
362         print_ps_string(r->name, outfp);
363         putc('\n', outfp);
364       }
365       else {
366         fputs("%%IncludeResource: ", outfp);
367         r->print_type_and_name(outfp);
368         putc('\n', outfp);
369       }
370     }
371     r->flags |= resource::NEEDED;
372   }
373   r->flags &= ~resource::BUSY;
374 }
375
376
377 #define PS_LINE_MAX 255
378 #define PS_MAGIC "%!PS-Adobe-"
379
380 static int ps_get_line(char *buf, FILE *fp)
381 {
382   int c = getc(fp);
383   if (c == EOF) {
384     buf[0] = '\0';
385     return 0;
386   }
387   current_lineno++;
388   int i = 0;
389   int err = 0;
390   while (c != '\r' && c != '\n' && c != EOF) {
391     if ((c < 0x1b && !white_space(c)) || c == 0x7f)
392       error("invalid input character code %1", int(c));
393     else if (i < PS_LINE_MAX)
394       buf[i++] = c;
395     else if (!err) {
396       err = 1;
397       error("PostScript file non-conforming "
398             "because length of line exceeds 255");
399     }
400     c = getc(fp);
401   }
402   buf[i++] = '\n';
403   buf[i] = '\0';
404   if (c == '\r') {
405     c = getc(fp);
406     if (c != EOF && c != '\n')
407       ungetc(c, fp);
408   }
409   return 1;
410 }
411
412 static int read_text_arg(const char **pp, string &res)
413 {
414   res.clear();
415   while (white_space(**pp))
416     *pp += 1;
417   if (**pp == '\0') {
418     error("missing argument");
419     return 0;
420   }
421   if (**pp != '(') {
422     for (; **pp != '\0' && !white_space(**pp); *pp += 1)
423       res += **pp;
424     return 1;
425   }
426   *pp += 1;
427   res.clear();
428   int level = 0;
429   for (;;) {
430     if (**pp == '\0' || **pp == '\r' || **pp == '\n') {
431       error("missing ')'");
432       return 0;
433     }
434     if (**pp == ')') {
435       if (level == 0) {
436         *pp += 1;
437         break;
438       }
439       res += **pp;
440       level--;
441     }
442     else if (**pp == '(') {
443       level++;
444       res += **pp;
445     }
446     else if (**pp == '\\') {
447       *pp += 1;
448       switch (**pp) {
449       case 'n':
450         res += '\n';
451         break;
452       case 'r':
453         res += '\n';
454         break;
455       case 't':
456         res += '\t';
457         break;
458       case 'b':
459         res += '\b';
460         break;
461       case 'f':
462         res += '\f';
463         break;
464       case '0':
465       case '1':
466       case '2':
467       case '3':
468       case '4':
469       case '5':
470       case '6':
471       case '7':
472         {
473           int val = **pp - '0';
474           if ((*pp)[1] >= '0' && (*pp)[1] <= '7') {
475             *pp += 1;
476             val = val*8 + (**pp - '0');
477             if ((*pp)[1] >= '0' && (*pp)[1] <= '7') {
478               *pp += 1;
479               val = val*8 + (**pp - '0');
480             }
481           }
482         }
483         break;
484       default:
485         res += **pp;
486         break;
487       }
488     }
489     else
490       res += **pp;
491     *pp += 1;
492   }
493   return 1;
494 }
495
496 resource *resource_manager::read_file_arg(const char **ptr)
497 {
498   string arg;
499   if (!read_text_arg(ptr, arg))
500     return 0;
501   return lookup_resource(RESOURCE_FILE, arg);
502 }
503
504 resource *resource_manager::read_font_arg(const char **ptr)
505 {
506   string arg;
507   if (!read_text_arg(ptr, arg))
508     return 0;
509   return lookup_resource(RESOURCE_FONT, arg);
510 }
511
512 resource *resource_manager::read_procset_arg(const char **ptr)
513 {
514   string arg;
515   if (!read_text_arg(ptr, arg))
516     return 0;
517   string version;
518   if (!read_text_arg(ptr, version))
519       return 0;
520   unsigned revision;
521   if (!read_uint_arg(ptr, &revision))
522       return 0;
523   return lookup_resource(RESOURCE_PROCSET, arg, version, revision);
524 }
525
526 resource *resource_manager::read_resource_arg(const char **ptr)
527 {
528   while (white_space(**ptr))
529     *ptr += 1;
530   const char *name = *ptr;
531   while (**ptr != '\0' && !white_space(**ptr))
532     *ptr += 1;
533   if (name == *ptr) {
534     error("missing resource type");
535     return 0;
536   }
537   int ri;
538   for (ri = 0; ri < NRESOURCES; ri++)
539     if (strlen(resource_table[ri]) == size_t(*ptr - name)
540         && memcmp(resource_table[ri], name, *ptr - name) == 0)
541       break;
542   if (ri >= NRESOURCES) {
543     error("unknown resource type");
544     return 0;
545   }
546   if (ri == RESOURCE_PROCSET)
547     return read_procset_arg(ptr);
548   string arg;
549   if (!read_text_arg(ptr, arg))
550     return 0;
551   return lookup_resource(resource_type(ri), arg);
552 }
553
554 static const char *matches_comment(const char *buf, const char *comment)
555 {
556   if (buf[0] != '%' || buf[1] != '%')
557     return 0;
558   for (buf += 2; *comment; comment++, buf++)
559     if (*buf != *comment)
560       return 0;
561   if (comment[-1] == ':')
562     return buf;
563   if (*buf == '\0' || white_space(*buf))
564     return buf;
565   return 0;
566 }
567
568 // Return 1 if the line should be copied out.
569
570 int resource_manager::do_begin_resource(const char *ptr, int, FILE *,
571                                         FILE *)
572 {
573   resource *r = read_resource_arg(&ptr);
574   if (r)
575     r->flags |= resource::SUPPLIED;
576   return 1;
577 }
578
579 int resource_manager::do_include_resource(const char *ptr, int rank, FILE *,
580                                           FILE *outfp)
581 {
582   resource *r = read_resource_arg(&ptr);
583   if (r) {
584     if (r->type == RESOURCE_FONT) {
585       if (rank >= 0)
586         supply_resource(r, rank + 1, outfp);
587       else
588         r->flags |= resource::FONT_NEEDED;
589     }
590     else
591       supply_resource(r, rank, outfp);
592   }
593   return 0;
594 }
595
596 int resource_manager::do_begin_document(const char *ptr, int, FILE *,
597                                         FILE *)
598 {
599   resource *r = read_file_arg(&ptr);
600   if (r)
601     r->flags |= resource::SUPPLIED;
602   return 1;
603 }
604
605 int resource_manager::do_include_document(const char *ptr, int rank, FILE *,
606                                           FILE *outfp)
607 {
608   resource *r = read_file_arg(&ptr);
609   if (r)
610     supply_resource(r, rank, outfp, 1);
611   return 0;
612 }
613
614 int resource_manager::do_begin_procset(const char *ptr, int, FILE *,
615                                        FILE *outfp)
616 {
617   resource *r = read_procset_arg(&ptr);
618   if (r) {
619     r->flags |= resource::SUPPLIED;
620     if (outfp) {
621       fputs("%%BeginResource: ", outfp);
622       r->print_type_and_name(outfp);
623       putc('\n', outfp);
624     }
625   }
626   return 0;
627 }
628
629 int resource_manager::do_include_procset(const char *ptr, int rank, FILE *,
630                                           FILE *outfp)
631 {
632   resource *r = read_procset_arg(&ptr);
633   if (r)
634     supply_resource(r, rank, outfp);
635   return 0;
636 }
637
638 int resource_manager::do_begin_file(const char *ptr, int, FILE *,
639                                     FILE *outfp)
640 {
641   resource *r = read_file_arg(&ptr);
642   if (r) {
643     r->flags |= resource::SUPPLIED;
644     if (outfp) {
645       fputs("%%BeginResource: ", outfp);
646       r->print_type_and_name(outfp);
647       putc('\n', outfp);
648     }
649   }
650   return 0;
651 }
652
653 int resource_manager::do_include_file(const char *ptr, int rank, FILE *,
654                                       FILE *outfp)
655 {
656   resource *r = read_file_arg(&ptr);
657   if (r)
658     supply_resource(r, rank, outfp);
659   return 0;
660 }
661
662 int resource_manager::do_begin_font(const char *ptr, int, FILE *,
663                                     FILE *outfp)
664 {
665   resource *r = read_font_arg(&ptr);
666   if (r) {
667     r->flags |= resource::SUPPLIED;
668     if (outfp) {
669       fputs("%%BeginResource: ", outfp);
670       r->print_type_and_name(outfp);
671       putc('\n', outfp);
672     }
673   }
674   return 0;
675 }
676
677 int resource_manager::do_include_font(const char *ptr, int rank, FILE *,
678                                       FILE *outfp)
679 {
680   resource *r = read_font_arg(&ptr);
681   if (r) {
682     if (rank >= 0)
683       supply_resource(r, rank + 1, outfp);
684     else
685       r->flags |= resource::FONT_NEEDED;
686   }
687   return 0;
688 }
689
690 int resource_manager::change_to_end_resource(const char *, int, FILE *,
691                                              FILE *outfp)
692 {
693   if (outfp)
694     fputs("%%EndResource\n", outfp);
695   return 0;
696 }
697
698 int resource_manager::do_begin_preview(const char *, int, FILE *fp, FILE *)
699 {
700   char buf[PS_LINE_MAX + 2];
701   do {
702     if (!ps_get_line(buf, fp)) {
703       error("end of file in preview section");
704       break;
705     }
706   } while (!matches_comment(buf, "EndPreview"));
707   return 0;
708 }
709
710 int read_one_of(const char **ptr, const char **s, int n)
711 {
712   while (white_space(**ptr))
713     *ptr += 1;
714   if (**ptr == '\0')
715     return -1;
716   const char *start = *ptr;
717   do {
718     ++(*ptr);
719   } while (**ptr != '\0' && !white_space(**ptr));
720   for (int i = 0; i < n; i++)
721     if (strlen(s[i]) == size_t(*ptr - start)
722         && memcmp(s[i], start, *ptr - start) == 0)
723       return i;
724   return -1;
725 }
726
727 void skip_possible_newline(const char *ptr, FILE *fp, FILE *outfp)
728 {
729   int c = getc(fp);
730   if (c == '\r') {
731     current_lineno++;
732     if (outfp)
733       putc(c, outfp);
734     int cc = getc(fp);
735     if (cc != '\n') {
736       if (cc != EOF)
737         ungetc(cc, fp);
738     }
739     else {
740       if (outfp)
741         putc(cc, outfp);
742     }
743   }
744   else if (c == '\n') {
745     current_lineno++;
746     if (outfp)
747       putc(c, outfp);
748   }
749   else if (c != EOF)
750     ungetc(c, fp);
751 }
752
753 int resource_manager::do_begin_data(const char *ptr, int, FILE *fp,
754                                     FILE *outfp)
755 {
756   while (white_space(*ptr))
757     ptr++;
758   const char *start = ptr;
759   unsigned numberof;
760   if (!read_uint_arg(&ptr, &numberof))
761     return 0;
762   static const char *types[] = { "Binary", "Hex", "ASCII" };
763   const int Binary = 0;
764   int type = 0;
765   static const char *units[] = { "Bytes", "Lines" };
766   const int Bytes = 0;
767   int unit = Bytes;
768   while (white_space(*ptr))
769     ptr++;
770   if (*ptr != '\0') {
771     type = read_one_of(&ptr, types, 3);
772     if (type < 0) {
773       error("bad data type");
774       return 0;
775     }
776     while (white_space(*ptr))
777       ptr++;
778     if (*ptr != '\0') {
779       unit = read_one_of(&ptr, units, 2);
780       if (unit < 0) {
781         error("expected `Bytes' or `Lines'");
782         return 0;
783       }
784     }
785   }
786   if (type != Binary)
787     return 1;
788   if (outfp) {
789     fputs("%%BeginData: ", outfp);
790     fputs(start, outfp);
791   }
792   if (numberof > 0) {
793     unsigned bytecount = 0;
794     unsigned linecount = 0;
795     do {
796       int c = getc(fp);
797       if (c == EOF) {
798         error("end of file within data section");
799         return 0;
800       }
801       if (outfp)
802         putc(c, outfp);
803       bytecount++;
804       if (c == '\r') {
805         int cc = getc(fp);
806         if (cc != '\n') {
807           linecount++;
808           current_lineno++;
809         }
810         if (cc != EOF)
811           ungetc(c, fp);
812       }
813       else if (c == '\n') {
814         linecount++;
815         current_lineno++;
816       }
817     } while ((unit == Bytes ? bytecount : linecount) < numberof);
818   }
819   skip_possible_newline(ptr, fp, outfp);
820   char buf[PS_LINE_MAX + 2];
821   if (!ps_get_line(buf, fp)) {
822     error("missing %%%%EndData line");
823     return 0;
824   }
825   if (!matches_comment(buf, "EndData"))
826     error("bad %%%%EndData line");
827   if (outfp)
828     fputs(buf, outfp);
829   return 0;
830 }
831
832 int resource_manager::do_begin_binary(const char *ptr, int, FILE *fp,
833                                       FILE *outfp)
834 {
835   if (!outfp)
836     return 0;
837   unsigned count;
838   if (!read_uint_arg(&ptr, &count))
839     return 0;
840   if (outfp)
841     fprintf(outfp, "%%%%BeginData: %u Binary Bytes\n", count);
842   while (count != 0) {
843     int c = getc(fp);
844     if (c == EOF) {
845       error("end of file within binary section");
846       return 0;
847     }
848     if (outfp)
849       putc(c, outfp);
850     --count;
851     if (c == '\r') {
852       int cc = getc(fp);
853       if (cc != '\n')
854         current_lineno++;
855       if (cc != EOF)
856         ungetc(cc, fp);
857     }
858     else if (c == '\n')
859       current_lineno++;
860   }
861   skip_possible_newline(ptr, fp, outfp);
862   char buf[PS_LINE_MAX + 2];
863   if (!ps_get_line(buf, fp)) {
864     error("missing %%%%EndBinary line");
865     return 0;
866   }
867   if (!matches_comment(buf, "EndBinary")) {
868     error("bad %%%%EndBinary line");
869     if (outfp)
870       fputs(buf, outfp);
871   }
872   else if (outfp)
873     fputs("%%EndData\n", outfp);
874   return 0;
875 }
876
877 static unsigned parse_extensions(const char *ptr)
878 {
879   unsigned flags = 0;
880   for (;;) {
881     while (white_space(*ptr))
882       ptr++;
883     if (*ptr == '\0')
884       break;
885     const char *name = ptr;
886     do {
887       ++ptr;
888     } while (*ptr != '\0' && !white_space(*ptr));
889     int i;
890     for (i = 0; i < NEXTENSIONS; i++)
891       if (strlen(extension_table[i]) == size_t(ptr - name)
892           && memcmp(extension_table[i], name, ptr - name) == 0) {
893         flags |= (1 << i);
894         break;
895       }
896     if (i >= NEXTENSIONS) {
897       string s(name, ptr - name);
898       s += '\0';
899       error("unknown extension `%1'", s.contents());
900     }
901   }
902   return flags;
903 }
904
905 // XXX if it has not been surrounded with {Begin,End}Document need to strip
906 // out Page: Trailer {Begin,End}Prolog {Begin,End}Setup sections.
907
908 // XXX Perhaps the decision whether to use BeginDocument or
909 // BeginResource: file should be postponed till we have seen
910 // the first line of the file.
911
912 void resource_manager::process_file(int rank, FILE *fp, const char *filename,
913                                     FILE *outfp)
914 {
915   // If none of these comments appear in the header section, and we are
916   // just analyzing the file (ie outfp is 0), then we can return immediately.
917   static const char *header_comment_table[] = {
918     "DocumentNeededResources:",
919     "DocumentSuppliedResources:",
920     "DocumentNeededFonts:",
921     "DocumentSuppliedFonts:",
922     "DocumentNeededProcSets:",
923     "DocumentSuppliedProcSets:",
924     "DocumentNeededFiles:",
925     "DocumentSuppliedFiles:",
926   };
927   
928   const int NHEADER_COMMENTS = (sizeof(header_comment_table)
929                                 / sizeof(header_comment_table[0]));
930   struct comment_info {
931     const char *name;
932     int (resource_manager::*proc)(const char *, int, FILE *, FILE *);
933   };
934
935   static comment_info comment_table[] = {
936     { "BeginResource:", &resource_manager::do_begin_resource },
937     { "IncludeResource:", &resource_manager::do_include_resource },
938     { "BeginDocument:", &resource_manager::do_begin_document },
939     { "IncludeDocument:", &resource_manager::do_include_document },
940     { "BeginProcSet:", &resource_manager::do_begin_procset },
941     { "IncludeProcSet:", &resource_manager::do_include_procset },
942     { "BeginFont:", &resource_manager::do_begin_font },
943     { "IncludeFont:", &resource_manager::do_include_font },
944     { "BeginFile:", &resource_manager::do_begin_file },
945     { "IncludeFile:", &resource_manager::do_include_file },
946     { "EndProcSet", &resource_manager::change_to_end_resource },
947     { "EndFont", &resource_manager::change_to_end_resource },
948     { "EndFile", &resource_manager::change_to_end_resource },
949     { "BeginPreview:", &resource_manager::do_begin_preview },
950     { "BeginData:", &resource_manager::do_begin_data },
951     { "BeginBinary:", &resource_manager::do_begin_binary },
952   };
953   
954   const int NCOMMENTS = sizeof(comment_table)/sizeof(comment_table[0]);
955   char buf[PS_LINE_MAX + 2];
956   int saved_lineno = current_lineno;
957   const char *saved_filename = current_filename;
958   current_filename = filename;
959   current_lineno = 0;
960   if (!ps_get_line(buf, fp)) {
961     current_filename = saved_filename;
962     current_lineno = saved_lineno;
963     return;
964   }
965   if (strlen(buf) < sizeof(PS_MAGIC) - 1
966       || memcmp(buf, PS_MAGIC, sizeof(PS_MAGIC) - 1) != 0) {
967     if (outfp) {
968       do {
969         if (!(broken_flags & STRIP_PERCENT_BANG)
970             || buf[0] != '%' || buf[1] != '!')
971           fputs(buf, outfp);
972       } while (ps_get_line(buf, fp));
973     }
974   }
975   else {
976     if (!(broken_flags & STRIP_PERCENT_BANG) && outfp)
977       fputs(buf, outfp);
978     int in_header = 1;
979     int interesting = 0;
980     int had_extensions_comment = 0;
981     int had_language_level_comment = 0;
982     for (;;) {
983       if (!ps_get_line(buf, fp))
984         break;
985       int copy_this_line = 1;
986       if (buf[0] == '%') {
987         if (buf[1] == '%') {
988           const char *ptr;
989           int i;
990           for (i = 0; i < NCOMMENTS; i++)
991             if ((ptr = matches_comment(buf, comment_table[i].name))) {
992               copy_this_line
993                 = (this->*(comment_table[i].proc))(ptr, rank, fp, outfp);
994               break;
995             }
996           if (i >= NCOMMENTS && in_header) {
997             if ((ptr = matches_comment(buf, "EndComments")))
998               in_header = 0;
999             else if (!had_extensions_comment
1000                      && (ptr = matches_comment(buf, "Extensions:"))) {
1001               extensions |= parse_extensions(ptr);
1002               // XXX handle possibility that next line is %%+
1003               had_extensions_comment = 1;
1004             }
1005             else if (!had_language_level_comment
1006                      && (ptr = matches_comment(buf, "LanguageLevel:"))) {
1007               unsigned ll;
1008               if (read_uint_arg(&ptr, &ll) && ll > language_level)
1009                 language_level = ll;
1010               had_language_level_comment = 1;
1011             }
1012             else {
1013               for (i = 0; i < NHEADER_COMMENTS; i++)
1014                 if (matches_comment(buf, header_comment_table[i])) {
1015                   interesting = 1;
1016                   break;
1017                 }
1018             }
1019           }
1020           if ((broken_flags & STRIP_STRUCTURE_COMMENTS)
1021               && (matches_comment(buf, "EndProlog")
1022                   || matches_comment(buf, "Page:")
1023                   || matches_comment(buf, "Trailer")))
1024             copy_this_line = 0;
1025         }
1026         else if (buf[1] == '!') {
1027           if (broken_flags & STRIP_PERCENT_BANG)
1028             copy_this_line = 0;
1029         }
1030       }
1031       else
1032         in_header = 0;
1033       if (!outfp && !in_header && !interesting)
1034         break;
1035       if (copy_this_line && outfp)
1036         fputs(buf, outfp);
1037     }
1038   }
1039   current_filename = saved_filename;
1040   current_lineno = saved_lineno;
1041 }
1042
1043 void resource_manager::read_download_file()
1044 {
1045   char *path = 0;
1046   FILE *fp = font::open_file("download", &path);
1047   if (!fp)
1048     fatal("can't find `download'");
1049   char buf[512];
1050   int lineno = 0;
1051   while (fgets(buf, sizeof(buf), fp)) {
1052     lineno++;
1053     char *p = strtok(buf, " \t\r\n");
1054     if (p == 0 || *p == '#')
1055       continue;
1056     char *q = strtok(0, " \t\r\n");
1057     if (!q)
1058       fatal_with_file_and_line(path, lineno, "missing filename");
1059     lookup_font(p)->filename = strsave(q);
1060   }
1061   a_delete path;
1062   fclose(fp);
1063 }
1064
1065 // XXX Can we share some code with ps_output::put_string()?
1066
1067 static void print_ps_string(const string &s, FILE *outfp)
1068 {
1069   int len = s.length();
1070   const char *str = s.contents();
1071   int funny = 0;
1072   if (str[0] == '(')
1073     funny = 1;
1074   else {
1075     for (int i = 0; i < len; i++)
1076       if (str[i] <= 040 || str[i] > 0176) {
1077         funny = 1;
1078         break;
1079       }
1080   }
1081   if (!funny) {
1082     put_string(s, outfp);
1083     return;
1084   }
1085   int level = 0;
1086   int i;
1087   for (i = 0; i < len; i++)
1088     if (str[i] == '(')
1089       level++;
1090     else if (str[i] == ')' && --level < 0)
1091       break;
1092   putc('(', outfp);
1093   for (i = 0; i < len; i++)
1094     switch (str[i]) {
1095     case '(':
1096     case ')':
1097       if (level != 0)
1098         putc('\\', outfp);
1099       putc(str[i], outfp);
1100       break;
1101     case '\\':
1102       fputs("\\\\", outfp);
1103       break;
1104     case '\n':
1105       fputs("\\n", outfp);
1106       break;
1107     case '\r':
1108       fputs("\\r", outfp);
1109       break;
1110     case '\t':
1111       fputs("\\t", outfp);
1112       break;
1113     case '\b':
1114       fputs("\\b", outfp);
1115       break;
1116     case '\f':
1117       fputs("\\f", outfp);
1118       break;
1119     default:
1120       if (str[i] < 040 || str[i] > 0176)
1121         fprintf(outfp, "\\%03o", str[i] & 0377);
1122       else
1123         putc(str[i], outfp);
1124       break;
1125     }
1126   putc(')', outfp);
1127 }
1128
1129 void resource_manager::print_extensions_comment(FILE *outfp)
1130 {
1131   if (extensions) {
1132     fputs("%%Extensions:", outfp);
1133     for (int i = 0; i < NEXTENSIONS; i++)
1134       if (extensions & (1 << i)) {
1135         putc(' ', outfp);
1136         fputs(extension_table[i], outfp);
1137       }
1138     putc('\n', outfp);
1139   }
1140 }
1141
1142 void resource_manager::print_language_level_comment(FILE *outfp)
1143 {
1144   if (language_level)
1145     fprintf(outfp, "%%%%LanguageLevel: %u\n", language_level);
1146 }
1147