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
6This file is part of groff.
7
8groff is free software; you can redistribute it and/or modify it under
9the terms of the GNU General Public License as published by the Free
10Software Foundation; either version 2, or (at your option) any later
11version.
12
13groff is distributed in the hope that it will be useful, but WITHOUT ANY
14WARRANTY; without even the implied warranty of MERCHANTABILITY or
15FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
16for more details.
17
18You should have received a copy of the GNU General Public License along
19with groff; see the file COPYING.  If not, write to the Free Software
20Foundation, 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
29extern "C" {
30  int putenv(const char *);
31}
32#endif /* NEED_DECLARATION_PUTENV */
33
34#define GROPS_PROLOGUE "prologue"
35
36static void print_ps_string(const string &s, FILE *outfp);
37
38cset white_space("\n\r \t\f");
39string an_empty_string;
40
41char 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
81const char *extension_table[] = {
82  "DPS",
83  "CMYK",
84  "Composite",
85  "FileSystem",
86};
87
88const int NEXTENSIONS = sizeof(extension_table)/sizeof(extension_table[0]);
89
90const char *resource_table[] = {
91  "font",
92  "procset",
93  "file",
94  "encoding",
95  "form",
96  "pattern",
97};
98
99const int NRESOURCES = sizeof(resource_table)/sizeof(resource_table[0]);
100
101static 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
124struct 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
139resource::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
151resource::~resource()
152{
153  a_delete filename;
154}
155
156void 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
168resource_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
184resource_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
193resource *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
213resource *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
228void resource_manager::need_font(const char *name)
229{
230  lookup_font(name)->flags |= resource::FONT_NEEDED;
231}
232
233typedef resource *Presource;	// Work around g++ bug.
234
235void 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
274void 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
293void 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
304void 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
330void 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
338void 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
418static 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
441static 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
525resource *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
533resource *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
541resource *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
555resource *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
583static 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
602int 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
611int 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
628int 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
637int 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
646int 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
661int 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
670int 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
685int 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
694int 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
709int 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
722int 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
730int 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
742int 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
759void 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
785int 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
864int 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
909static 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
944void 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
1075void 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
1099static 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
1161void 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
1174void resource_manager::print_language_level_comment(FILE *outfp)
1175{
1176  if (language_level)
1177    fprintf(outfp, "%%%%LanguageLevel: %u\n", language_level);
1178}
1179