2 /* Copyright (C) 1989, 1990, 1991, 1992, 2000, 2001, 2002
3 Free Software Foundation, Inc.
4 Written by James Clark (jjc@jclark.com)
6 This file is part of groff.
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
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
18 You should have received a copy of the GNU General Public License along
19 with groff; see the file COPYING. If not, write to the Free Software
20 Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */
23 #include "stringclass.h"
28 #ifdef NEED_DECLARATION_PUTENV
30 int putenv(const char *);
32 #endif /* NEED_DECLARATION_PUTENV */
34 #define GROPS_PROLOGUE "prologue"
36 static void print_ps_string(const string &s, FILE *outfp);
38 cset white_space("\n\r \t");
39 string an_empty_string;
41 const char *extension_table[] = {
48 const int NEXTENSIONS = sizeof(extension_table)/sizeof(extension_table[0]);
50 const char *resource_table[] = {
59 const int NRESOURCES = sizeof(resource_table)/sizeof(resource_table[0]);
61 static int read_uint_arg(const char **pp, unsigned *res)
63 while (white_space(**pp))
66 error("missing argument");
69 const char *start = *pp;
71 long n = strtol(start, (char **)pp, 10);
72 if (n == 0 && *pp == start) {
73 error("not an integer");
77 error("argument must not be negative");
88 enum { NEEDED = 01, SUPPLIED = 02, FONT_NEEDED = 04, BUSY = 010 };
94 resource(resource_type, string &, string & = an_empty_string, unsigned = 0);
96 void print_type_and_name(FILE *outfp);
99 resource::resource(resource_type t, string &n, string &v, unsigned r)
100 : next(0), type(t), flags(0), revision(r), filename(0), rank(-1)
104 if (type == RESOURCE_FILE) {
105 if (name.search('\0') >= 0)
106 error("filename contains a character with code 0");
107 filename = name.extract();
111 resource::~resource()
116 void resource::print_type_and_name(FILE *outfp)
118 fputs(resource_table[type], outfp);
120 print_ps_string(name, outfp);
121 if (type == RESOURCE_PROCSET) {
123 print_ps_string(version, outfp);
124 fprintf(outfp, " %u", revision);
128 resource_manager::resource_manager()
129 : extensions(0), language_level(0), resource_list(0)
131 read_download_file();
132 string procset_name("grops");
133 extern const char *version_string;
134 extern const char *revision_string;
135 unsigned revision_uint;
136 if ( !read_uint_arg( &revision_string, &revision_uint) )
138 string procset_version(version_string);
139 procset_resource = lookup_resource(RESOURCE_PROCSET, procset_name,
140 procset_version, revision_uint);
141 procset_resource->flags |= resource::SUPPLIED;
144 resource_manager::~resource_manager()
146 while (resource_list) {
147 resource *tem = resource_list;
148 resource_list = resource_list->next;
153 resource *resource_manager::lookup_resource(resource_type type,
159 for (r = resource_list; r; r = r->next)
162 && r->version == version
163 && r->revision == revision)
165 r = new resource(type, name, version, revision);
166 r->next = resource_list;
171 // Just a specialized version of lookup_resource().
173 resource *resource_manager::lookup_font(const char *name)
176 for (r = resource_list; r; r = r->next)
177 if (r->type == RESOURCE_FONT
178 && strlen(name) == (size_t)r->name.length()
179 && memcmp(name, r->name.contents(), r->name.length()) == 0)
182 r = new resource(RESOURCE_FONT, s);
183 r->next = resource_list;
188 void resource_manager::need_font(const char *name)
190 lookup_font(name)->flags |= resource::FONT_NEEDED;
193 typedef resource *Presource; // Work around g++ bug.
195 void resource_manager::document_setup(ps_output &out)
199 for (r = resource_list; r; r = r->next)
200 if (r->rank >= nranks)
201 nranks = r->rank + 1;
203 // Sort resource_list in reverse order of rank.
204 Presource *head = new Presource[nranks + 1];
205 Presource **tail = new Presource *[nranks + 1];
207 for (i = 0; i < nranks + 1; i++) {
211 for (r = resource_list; r; r = r->next) {
212 i = r->rank < 0 ? 0 : r->rank + 1;
214 tail[i] = &(*tail[i])->next;
217 for (i = 0; i < nranks + 1; i++)
219 *tail[i] = resource_list;
220 resource_list = head[i];
225 for (r = resource_list; r; r = r->next)
227 assert(r->rank >= r->next->rank);
228 for (r = resource_list; r; r = r->next)
229 if (r->type == RESOURCE_FONT && r->rank >= 0)
230 supply_resource(r, -1, out.get_file());
234 void resource_manager::print_resources_comment(unsigned flag, FILE *outfp)
237 for (resource *r = resource_list; r; r = r->next)
238 if (r->flags & flag) {
240 fputs("%%+ ", outfp);
242 fputs(flag == resource::NEEDED
243 ? "%%DocumentNeededResources: "
244 : "%%DocumentSuppliedResources: ",
248 r->print_type_and_name(outfp);
253 void resource_manager::print_header_comments(ps_output &out)
255 for (resource *r = resource_list; r; r = r->next)
256 if (r->type == RESOURCE_FONT && (r->flags & resource::FONT_NEEDED))
257 supply_resource(r, 0, 0);
258 print_resources_comment(resource::NEEDED, out.get_file());
259 print_resources_comment(resource::SUPPLIED, out.get_file());
260 print_language_level_comment(out.get_file());
261 print_extensions_comment(out.get_file());
264 void resource_manager::output_prolog(ps_output &out)
266 FILE *outfp = out.get_file();
269 if (!getenv("GROPS_PROLOGUE")) {
270 string e = "GROPS_PROLOGUE";
274 if (putenv(strsave(e.contents())))
275 fatal("putenv failed");
277 char *prologue = getenv("GROPS_PROLOGUE");
278 FILE *fp = font::open_file(prologue, &path);
280 fatal("can't find `%1'", prologue);
281 fputs("%%BeginResource: ", outfp);
282 procset_resource->print_type_and_name(outfp);
284 process_file(-1, fp, path, outfp);
287 fputs("%%EndResource\n", outfp);
290 void resource_manager::import_file(const char *filename, ps_output &out)
293 string name(filename);
294 resource *r = lookup_resource(RESOURCE_FILE, name);
295 supply_resource(r, -1, out.get_file(), 1);
298 void resource_manager::supply_resource(resource *r, int rank, FILE *outfp,
301 if (r->flags & resource::BUSY) {
303 fatal("loop detected in dependency graph for %1 `%2'",
304 resource_table[r->type],
307 r->flags |= resource::BUSY;
312 if (r->filename != 0) {
313 if (r->type == RESOURCE_FONT) {
314 fp = font::open_file(r->filename, &path);
316 error("can't find `%1'", r->filename);
317 a_delete r->filename;
323 fp = fopen(r->filename, "r");
325 error("can't open `%1': %2", r->filename, strerror(errno));
326 a_delete r->filename;
335 if (r->type == RESOURCE_FILE && is_document) {
336 fputs("%%BeginDocument: ", outfp);
337 print_ps_string(r->name, outfp);
341 fputs("%%BeginResource: ", outfp);
342 r->print_type_and_name(outfp);
346 process_file(rank, fp, path, outfp);
348 if (r->type == RESOURCE_FONT)
351 if (r->type == RESOURCE_FILE && is_document)
352 fputs("%%EndDocument\n", outfp);
354 fputs("%%EndResource\n", outfp);
356 r->flags |= resource::SUPPLIED;
360 if (r->type == RESOURCE_FILE && is_document) {
361 fputs("%%IncludeDocument: ", outfp);
362 print_ps_string(r->name, outfp);
366 fputs("%%IncludeResource: ", outfp);
367 r->print_type_and_name(outfp);
371 r->flags |= resource::NEEDED;
373 r->flags &= ~resource::BUSY;
377 #define PS_LINE_MAX 255
378 #define PS_MAGIC "%!PS-Adobe-"
380 static int ps_get_line(char *buf, FILE *fp)
390 while (c != '\r' && c != '\n' && c != EOF) {
391 if ((c < 0x1b && !white_space(c)) || c == 0x7f)
392 error("invalid input character code %1", int(c));
393 else if (i < PS_LINE_MAX)
397 error("PostScript file non-conforming "
398 "because length of line exceeds 255");
406 if (c != EOF && c != '\n')
412 static int read_text_arg(const char **pp, string &res)
415 while (white_space(**pp))
418 error("missing argument");
422 for (; **pp != '\0' && !white_space(**pp); *pp += 1)
430 if (**pp == '\0' || **pp == '\r' || **pp == '\n') {
431 error("missing ')'");
442 else if (**pp == '(') {
446 else if (**pp == '\\') {
473 int val = **pp - '0';
474 if ((*pp)[1] >= '0' && (*pp)[1] <= '7') {
476 val = val*8 + (**pp - '0');
477 if ((*pp)[1] >= '0' && (*pp)[1] <= '7') {
479 val = val*8 + (**pp - '0');
496 resource *resource_manager::read_file_arg(const char **ptr)
499 if (!read_text_arg(ptr, arg))
501 return lookup_resource(RESOURCE_FILE, arg);
504 resource *resource_manager::read_font_arg(const char **ptr)
507 if (!read_text_arg(ptr, arg))
509 return lookup_resource(RESOURCE_FONT, arg);
512 resource *resource_manager::read_procset_arg(const char **ptr)
515 if (!read_text_arg(ptr, arg))
518 if (!read_text_arg(ptr, version))
521 if (!read_uint_arg(ptr, &revision))
523 return lookup_resource(RESOURCE_PROCSET, arg, version, revision);
526 resource *resource_manager::read_resource_arg(const char **ptr)
528 while (white_space(**ptr))
530 const char *name = *ptr;
531 while (**ptr != '\0' && !white_space(**ptr))
534 error("missing resource type");
538 for (ri = 0; ri < NRESOURCES; ri++)
539 if (strlen(resource_table[ri]) == size_t(*ptr - name)
540 && memcmp(resource_table[ri], name, *ptr - name) == 0)
542 if (ri >= NRESOURCES) {
543 error("unknown resource type");
546 if (ri == RESOURCE_PROCSET)
547 return read_procset_arg(ptr);
549 if (!read_text_arg(ptr, arg))
551 return lookup_resource(resource_type(ri), arg);
554 static const char *matches_comment(const char *buf, const char *comment)
556 if (buf[0] != '%' || buf[1] != '%')
558 for (buf += 2; *comment; comment++, buf++)
559 if (*buf != *comment)
561 if (comment[-1] == ':')
563 if (*buf == '\0' || white_space(*buf))
568 // Return 1 if the line should be copied out.
570 int resource_manager::do_begin_resource(const char *ptr, int, FILE *,
573 resource *r = read_resource_arg(&ptr);
575 r->flags |= resource::SUPPLIED;
579 int resource_manager::do_include_resource(const char *ptr, int rank, FILE *,
582 resource *r = read_resource_arg(&ptr);
584 if (r->type == RESOURCE_FONT) {
586 supply_resource(r, rank + 1, outfp);
588 r->flags |= resource::FONT_NEEDED;
591 supply_resource(r, rank, outfp);
596 int resource_manager::do_begin_document(const char *ptr, int, FILE *,
599 resource *r = read_file_arg(&ptr);
601 r->flags |= resource::SUPPLIED;
605 int resource_manager::do_include_document(const char *ptr, int rank, FILE *,
608 resource *r = read_file_arg(&ptr);
610 supply_resource(r, rank, outfp, 1);
614 int resource_manager::do_begin_procset(const char *ptr, int, FILE *,
617 resource *r = read_procset_arg(&ptr);
619 r->flags |= resource::SUPPLIED;
621 fputs("%%BeginResource: ", outfp);
622 r->print_type_and_name(outfp);
629 int resource_manager::do_include_procset(const char *ptr, int rank, FILE *,
632 resource *r = read_procset_arg(&ptr);
634 supply_resource(r, rank, outfp);
638 int resource_manager::do_begin_file(const char *ptr, int, FILE *,
641 resource *r = read_file_arg(&ptr);
643 r->flags |= resource::SUPPLIED;
645 fputs("%%BeginResource: ", outfp);
646 r->print_type_and_name(outfp);
653 int resource_manager::do_include_file(const char *ptr, int rank, FILE *,
656 resource *r = read_file_arg(&ptr);
658 supply_resource(r, rank, outfp);
662 int resource_manager::do_begin_font(const char *ptr, int, FILE *,
665 resource *r = read_font_arg(&ptr);
667 r->flags |= resource::SUPPLIED;
669 fputs("%%BeginResource: ", outfp);
670 r->print_type_and_name(outfp);
677 int resource_manager::do_include_font(const char *ptr, int rank, FILE *,
680 resource *r = read_font_arg(&ptr);
683 supply_resource(r, rank + 1, outfp);
685 r->flags |= resource::FONT_NEEDED;
690 int resource_manager::change_to_end_resource(const char *, int, FILE *,
694 fputs("%%EndResource\n", outfp);
698 int resource_manager::do_begin_preview(const char *, int, FILE *fp, FILE *)
700 char buf[PS_LINE_MAX + 2];
702 if (!ps_get_line(buf, fp)) {
703 error("end of file in preview section");
706 } while (!matches_comment(buf, "EndPreview"));
710 int read_one_of(const char **ptr, const char **s, int n)
712 while (white_space(**ptr))
716 const char *start = *ptr;
719 } while (**ptr != '\0' && !white_space(**ptr));
720 for (int i = 0; i < n; i++)
721 if (strlen(s[i]) == size_t(*ptr - start)
722 && memcmp(s[i], start, *ptr - start) == 0)
727 void skip_possible_newline(const char *ptr, FILE *fp, FILE *outfp)
744 else if (c == '\n') {
753 int resource_manager::do_begin_data(const char *ptr, int, FILE *fp,
756 while (white_space(*ptr))
758 const char *start = ptr;
760 if (!read_uint_arg(&ptr, &numberof))
762 static const char *types[] = { "Binary", "Hex", "ASCII" };
763 const int Binary = 0;
765 static const char *units[] = { "Bytes", "Lines" };
768 while (white_space(*ptr))
771 type = read_one_of(&ptr, types, 3);
773 error("bad data type");
776 while (white_space(*ptr))
779 unit = read_one_of(&ptr, units, 2);
781 error("expected `Bytes' or `Lines'");
789 fputs("%%BeginData: ", outfp);
793 unsigned bytecount = 0;
794 unsigned linecount = 0;
798 error("end of file within data section");
813 else if (c == '\n') {
817 } while ((unit == Bytes ? bytecount : linecount) < numberof);
819 skip_possible_newline(ptr, fp, outfp);
820 char buf[PS_LINE_MAX + 2];
821 if (!ps_get_line(buf, fp)) {
822 error("missing %%%%EndData line");
825 if (!matches_comment(buf, "EndData"))
826 error("bad %%%%EndData line");
832 int resource_manager::do_begin_binary(const char *ptr, int, FILE *fp,
838 if (!read_uint_arg(&ptr, &count))
841 fprintf(outfp, "%%%%BeginData: %u Binary Bytes\n", count);
845 error("end of file within binary section");
861 skip_possible_newline(ptr, fp, outfp);
862 char buf[PS_LINE_MAX + 2];
863 if (!ps_get_line(buf, fp)) {
864 error("missing %%%%EndBinary line");
867 if (!matches_comment(buf, "EndBinary")) {
868 error("bad %%%%EndBinary line");
873 fputs("%%EndData\n", outfp);
877 static unsigned parse_extensions(const char *ptr)
881 while (white_space(*ptr))
885 const char *name = ptr;
888 } while (*ptr != '\0' && !white_space(*ptr));
890 for (i = 0; i < NEXTENSIONS; i++)
891 if (strlen(extension_table[i]) == size_t(ptr - name)
892 && memcmp(extension_table[i], name, ptr - name) == 0) {
896 if (i >= NEXTENSIONS) {
897 string s(name, ptr - name);
899 error("unknown extension `%1'", s.contents());
905 // XXX if it has not been surrounded with {Begin,End}Document need to strip
906 // out Page: Trailer {Begin,End}Prolog {Begin,End}Setup sections.
908 // XXX Perhaps the decision whether to use BeginDocument or
909 // BeginResource: file should be postponed till we have seen
910 // the first line of the file.
912 void resource_manager::process_file(int rank, FILE *fp, const char *filename,
915 // If none of these comments appear in the header section, and we are
916 // just analyzing the file (ie outfp is 0), then we can return immediately.
917 static const char *header_comment_table[] = {
918 "DocumentNeededResources:",
919 "DocumentSuppliedResources:",
920 "DocumentNeededFonts:",
921 "DocumentSuppliedFonts:",
922 "DocumentNeededProcSets:",
923 "DocumentSuppliedProcSets:",
924 "DocumentNeededFiles:",
925 "DocumentSuppliedFiles:",
928 const int NHEADER_COMMENTS = (sizeof(header_comment_table)
929 / sizeof(header_comment_table[0]));
930 struct comment_info {
932 int (resource_manager::*proc)(const char *, int, FILE *, FILE *);
935 static comment_info comment_table[] = {
936 { "BeginResource:", &resource_manager::do_begin_resource },
937 { "IncludeResource:", &resource_manager::do_include_resource },
938 { "BeginDocument:", &resource_manager::do_begin_document },
939 { "IncludeDocument:", &resource_manager::do_include_document },
940 { "BeginProcSet:", &resource_manager::do_begin_procset },
941 { "IncludeProcSet:", &resource_manager::do_include_procset },
942 { "BeginFont:", &resource_manager::do_begin_font },
943 { "IncludeFont:", &resource_manager::do_include_font },
944 { "BeginFile:", &resource_manager::do_begin_file },
945 { "IncludeFile:", &resource_manager::do_include_file },
946 { "EndProcSet", &resource_manager::change_to_end_resource },
947 { "EndFont", &resource_manager::change_to_end_resource },
948 { "EndFile", &resource_manager::change_to_end_resource },
949 { "BeginPreview:", &resource_manager::do_begin_preview },
950 { "BeginData:", &resource_manager::do_begin_data },
951 { "BeginBinary:", &resource_manager::do_begin_binary },
954 const int NCOMMENTS = sizeof(comment_table)/sizeof(comment_table[0]);
955 char buf[PS_LINE_MAX + 2];
956 int saved_lineno = current_lineno;
957 const char *saved_filename = current_filename;
958 current_filename = filename;
960 if (!ps_get_line(buf, fp)) {
961 current_filename = saved_filename;
962 current_lineno = saved_lineno;
965 if (strlen(buf) < sizeof(PS_MAGIC) - 1
966 || memcmp(buf, PS_MAGIC, sizeof(PS_MAGIC) - 1) != 0) {
969 if (!(broken_flags & STRIP_PERCENT_BANG)
970 || buf[0] != '%' || buf[1] != '!')
972 } while (ps_get_line(buf, fp));
976 if (!(broken_flags & STRIP_PERCENT_BANG) && outfp)
980 int had_extensions_comment = 0;
981 int had_language_level_comment = 0;
983 if (!ps_get_line(buf, fp))
985 int copy_this_line = 1;
990 for (i = 0; i < NCOMMENTS; i++)
991 if ((ptr = matches_comment(buf, comment_table[i].name))) {
993 = (this->*(comment_table[i].proc))(ptr, rank, fp, outfp);
996 if (i >= NCOMMENTS && in_header) {
997 if ((ptr = matches_comment(buf, "EndComments")))
999 else if (!had_extensions_comment
1000 && (ptr = matches_comment(buf, "Extensions:"))) {
1001 extensions |= parse_extensions(ptr);
1002 // XXX handle possibility that next line is %%+
1003 had_extensions_comment = 1;
1005 else if (!had_language_level_comment
1006 && (ptr = matches_comment(buf, "LanguageLevel:"))) {
1008 if (read_uint_arg(&ptr, &ll) && ll > language_level)
1009 language_level = ll;
1010 had_language_level_comment = 1;
1013 for (i = 0; i < NHEADER_COMMENTS; i++)
1014 if (matches_comment(buf, header_comment_table[i])) {
1020 if ((broken_flags & STRIP_STRUCTURE_COMMENTS)
1021 && (matches_comment(buf, "EndProlog")
1022 || matches_comment(buf, "Page:")
1023 || matches_comment(buf, "Trailer")))
1026 else if (buf[1] == '!') {
1027 if (broken_flags & STRIP_PERCENT_BANG)
1033 if (!outfp && !in_header && !interesting)
1035 if (copy_this_line && outfp)
1039 current_filename = saved_filename;
1040 current_lineno = saved_lineno;
1043 void resource_manager::read_download_file()
1046 FILE *fp = font::open_file("download", &path);
1048 fatal("can't find `download'");
1051 while (fgets(buf, sizeof(buf), fp)) {
1053 char *p = strtok(buf, " \t\r\n");
1054 if (p == 0 || *p == '#')
1056 char *q = strtok(0, " \t\r\n");
1058 fatal_with_file_and_line(path, lineno, "missing filename");
1059 lookup_font(p)->filename = strsave(q);
1065 // XXX Can we share some code with ps_output::put_string()?
1067 static void print_ps_string(const string &s, FILE *outfp)
1069 int len = s.length();
1070 const char *str = s.contents();
1075 for (int i = 0; i < len; i++)
1076 if (str[i] <= 040 || str[i] > 0176) {
1082 put_string(s, outfp);
1087 for (i = 0; i < len; i++)
1090 else if (str[i] == ')' && --level < 0)
1093 for (i = 0; i < len; i++)
1099 putc(str[i], outfp);
1102 fputs("\\\\", outfp);
1105 fputs("\\n", outfp);
1108 fputs("\\r", outfp);
1111 fputs("\\t", outfp);
1114 fputs("\\b", outfp);
1117 fputs("\\f", outfp);
1120 if (str[i] < 040 || str[i] > 0176)
1121 fprintf(outfp, "\\%03o", str[i] & 0377);
1123 putc(str[i], outfp);
1129 void resource_manager::print_extensions_comment(FILE *outfp)
1132 fputs("%%Extensions:", outfp);
1133 for (int i = 0; i < NEXTENSIONS; i++)
1134 if (extensions & (1 << i)) {
1136 fputs(extension_table[i], outfp);
1142 void resource_manager::print_language_level_comment(FILE *outfp)
1145 fprintf(outfp, "%%%%LanguageLevel: %u\n", language_level);