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