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