Initial import from FreeBSD RELENG_4:
[dragonfly.git] / contrib / groff / src / devices / grops / ps.cc
1 // -*- C++ -*-
2 /* Copyright (C) 1989, 1990, 1991, 1992, 2000, 2001, 2002
3    Free Software Foundation, Inc.
4      Written by James Clark (jjc@jclark.com)
5
6 This file is part of groff.
7
8 groff is free software; you can redistribute it and/or modify it under
9 the terms of the GNU General Public License as published by the Free
10 Software Foundation; either version 2, or (at your option) any later
11 version.
12
13 groff is distributed in the hope that it will be useful, but WITHOUT ANY
14 WARRANTY; without even the implied warranty of MERCHANTABILITY or
15 FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
16 for more details.
17
18 You should have received a copy of the GNU General Public License along
19 with groff; see the file COPYING.  If not, write to the Free Software
20 Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */
21
22 #include "driver.h"
23 #include "stringclass.h"
24 #include "cset.h"
25 #include "nonposix.h"
26 #include "paper.h"
27
28 #include "ps.h"
29 #include <time.h>
30
31 #ifdef NEED_DECLARATION_PUTENV
32 extern "C" {
33   int putenv(const char *);
34 }
35 #endif /* NEED_DECLARATION_PUTENV */
36
37 extern "C" const char *Version_string;
38
39 static int landscape_flag = 0;
40 static int manual_feed_flag = 0;
41 static int ncopies = 1;
42 static int linewidth = -1;
43 // Non-zero means generate PostScript code that guesses the paper
44 // length using the imageable area.
45 static int guess_flag = 0;
46 static double user_paper_length = 0;
47
48 // Non-zero if -b was specified on the command line.
49 static int bflag = 0;
50 unsigned broken_flags = 0;
51
52 #define DEFAULT_LINEWIDTH 40    /* in ems/1000 */
53 #define MAX_LINE_LENGTH 72
54 #define FILL_MAX 1000
55
56 const char *const dict_name = "grops";
57 const char *const defs_dict_name = "DEFS";
58 const int DEFS_DICT_SPARE = 50;
59
60 double degrees(double r)
61 {
62   return r*180.0/PI;
63 }
64
65 double radians(double d)
66 {
67   return d*PI/180.0;
68 }
69
70 inline double transform_fill(int fill)
71 {
72   return 1 - fill/double(FILL_MAX);
73 }
74
75 // This is used for testing whether a character should be output in the
76 // PostScript file using \nnn, so we really want the character to be
77 // less than 0200.
78
79 inline int is_ascii(char c)
80 {
81   return (unsigned char)c < 0200;
82 }
83
84 ps_output::ps_output(FILE *f, int n)
85 : fp(f), col(0), max_line_length(n), need_space(0), fixed_point(0)
86 {
87 }
88
89 ps_output &ps_output::set_file(FILE *f)
90 {
91   fp = f;
92   col = 0;
93   return *this;
94 }
95
96 ps_output &ps_output::copy_file(FILE *infp)
97 {
98   int c;
99   while ((c = getc(infp)) != EOF)
100     putc(c, fp);
101   return *this;
102 }
103
104 ps_output &ps_output::end_line()
105 {
106   if (col != 0) {
107     putc('\n', fp);
108     col = 0;
109     need_space = 0;
110   }
111   return *this;
112 }
113
114 ps_output &ps_output::special(const char *s)
115 {
116   if (s == 0 || *s == '\0')
117     return *this;
118   if (col != 0) {
119     putc('\n', fp);
120     col = 0;
121   }
122   fputs(s, fp);
123   if (strchr(s, '\0')[-1] != '\n')
124     putc('\n', fp);
125   need_space = 0;
126   return *this;
127 }
128
129 ps_output &ps_output::simple_comment(const char *s)
130 {
131   if (col != 0)
132     putc('\n', fp);
133   putc('%', fp);
134   putc('%', fp);
135   fputs(s, fp);
136   putc('\n', fp);
137   col = 0;
138   need_space = 0;
139   return *this;
140 }
141
142 ps_output &ps_output::begin_comment(const char *s)
143 {
144   if (col != 0)
145     putc('\n', fp);
146   putc('%', fp);
147   putc('%', fp);
148   fputs(s, fp);
149   col = 2 + strlen(s);
150   return *this;
151 }
152
153 ps_output &ps_output::end_comment()
154 {
155   if (col != 0) {
156     putc('\n', fp);
157     col = 0;
158   }
159   need_space = 0;
160   return *this;
161 }
162
163 ps_output &ps_output::comment_arg(const char *s)
164 {
165   int len = strlen(s);
166   if (col + len + 1 > max_line_length) {
167     putc('\n', fp);
168     fputs("%%+", fp);
169     col = 3;
170   }
171   putc(' ',  fp);
172   fputs(s, fp);
173   col += len + 1;
174   return *this;
175 }
176
177 ps_output &ps_output::set_fixed_point(int n)
178 {
179   assert(n >= 0 && n <= 10);
180   fixed_point = n;
181   return *this;
182 }
183
184 ps_output &ps_output::put_delimiter(char c)
185 {
186   if (col + 1 > max_line_length) {
187     putc('\n', fp);
188     col = 0;
189   }
190   putc(c, fp);
191   col++;
192   need_space = 0;
193   return *this;
194 }
195
196 ps_output &ps_output::put_string(const char *s, int n)
197 {
198   int len = 0;
199   int i;
200   for (i = 0; i < n; i++) {
201     char c = s[i];
202     if (is_ascii(c) && csprint(c)) {
203       if (c == '(' || c == ')' || c == '\\')
204         len += 2;
205       else
206         len += 1;
207     }
208     else
209       len += 4;
210   }
211   if (len > n*2) {
212     if (col + n*2 + 2 > max_line_length && n*2 + 2 <= max_line_length) {
213       putc('\n', fp);
214       col = 0;
215     }
216     if (col + 1 > max_line_length) {
217       putc('\n', fp);
218       col = 0;
219     }
220     putc('<', fp);
221     col++;
222     for (i = 0; i < n; i++) {
223       if (col + 2 > max_line_length) {
224         putc('\n', fp);
225         col = 0;
226       }
227       fprintf(fp, "%02x", s[i] & 0377);
228       col += 2;
229     }
230     putc('>', fp);
231     col++;
232   }
233   else {
234     if (col + len + 2 > max_line_length && len + 2 <= max_line_length) {
235       putc('\n', fp);
236       col = 0;
237     }
238     if (col + 2 > max_line_length) {
239       putc('\n', fp);
240       col = 0;
241     }
242     putc('(', fp);
243     col++;
244     for (i = 0; i < n; i++) {
245       char c = s[i];
246       if (is_ascii(c) && csprint(c)) {
247         if (c == '(' || c == ')' || c == '\\')
248           len = 2;
249         else
250           len = 1;
251       }
252       else
253         len = 4;
254       if (col + len + 1 > max_line_length) {
255         putc('\\', fp);
256         putc('\n', fp);
257         col = 0;
258       }
259       switch (len) {
260       case 1:
261         putc(c, fp);
262         break;
263       case 2:
264         putc('\\', fp);
265         putc(c, fp);
266         break;
267       case 4:
268         fprintf(fp, "\\%03o", c & 0377);
269         break;
270       default:
271         assert(0);
272       }
273       col += len;
274     }
275     putc(')', fp);
276     col++;
277   }
278   need_space = 0;
279   return *this;
280 }
281
282 ps_output &ps_output::put_number(int n)
283 {
284   char buf[1 + INT_DIGITS + 1];
285   sprintf(buf, "%d", n);
286   int len = strlen(buf);
287   if (col > 0 && col + len + need_space > max_line_length) {
288     putc('\n', fp);
289     col = 0;
290     need_space = 0;
291   }
292   if (need_space) {
293     putc(' ', fp);
294     col++;
295   }
296   fputs(buf, fp);
297   col += len;
298   need_space = 1;
299   return *this;
300 }
301
302 ps_output &ps_output::put_fix_number(int i)
303 {
304   const char *p = if_to_a(i, fixed_point);
305   int len = strlen(p);
306   if (col > 0 && col + len + need_space > max_line_length) {
307     putc('\n', fp);
308     col = 0;
309     need_space = 0;
310   }
311   if (need_space) {
312     putc(' ', fp);
313     col++;
314   }
315   fputs(p, fp);
316   col += len;
317   need_space = 1;
318   return *this;
319 }
320
321 ps_output &ps_output::put_float(double d)
322 {
323   char buf[128];
324   sprintf(buf, "%.3g", d);
325   int len = strlen(buf);
326   if (col > 0 && col + len + need_space > max_line_length) {
327     putc('\n', fp);
328     col = 0;
329     need_space = 0;
330   }
331   if (need_space) {
332     putc(' ', fp);
333     col++;
334   }
335   fputs(buf, fp);
336   col += len;
337   need_space = 1;
338   return *this;
339 }
340
341 ps_output &ps_output::put_symbol(const char *s)
342 {
343   int len = strlen(s);
344   if (col > 0 && col + len + need_space > max_line_length) {
345     putc('\n', fp);
346     col = 0;
347     need_space = 0;
348   }
349   if (need_space) {
350     putc(' ', fp);
351     col++;
352   }
353   fputs(s, fp);
354   col += len;
355   need_space = 1;
356   return *this;
357 }
358
359 ps_output &ps_output::put_color(unsigned int c)
360 {
361   char buf[128];
362   sprintf(buf, "%.3g", double(c) / color::MAX_COLOR_VAL);
363   int len = strlen(buf);
364   if (col > 0 && col + len + need_space > max_line_length) {
365     putc('\n', fp);
366     col = 0;
367     need_space = 0;
368   }
369   if (need_space) {
370     putc(' ', fp);
371     col++;
372   }
373   fputs(buf, fp);
374   col += len;
375   need_space = 1;
376   return *this;
377 }
378
379 ps_output &ps_output::put_literal_symbol(const char *s)
380 {
381   int len = strlen(s);
382   if (col > 0 && col + len + 1 > max_line_length) {
383     putc('\n', fp);
384     col = 0;
385   }
386   putc('/', fp);
387   fputs(s, fp);
388   col += len + 1;
389   need_space = 1;
390   return *this;
391 }
392
393 class ps_font : public font {
394   ps_font(const char *);
395 public:
396   int encoding_index;
397   char *encoding;
398   char *reencoded_name;
399   ~ps_font();
400   void handle_unknown_font_command(const char *command, const char *arg,
401                                    const char *filename, int lineno);
402   static ps_font *load_ps_font(const char *);
403 };
404
405 ps_font *ps_font::load_ps_font(const char *s)
406 {
407   ps_font *f = new ps_font(s);
408   if (!f->load()) {
409     delete f;
410     return 0;
411   }
412   return f;
413 }
414
415 ps_font::ps_font(const char *nm)
416 : font(nm), encoding_index(-1), encoding(0), reencoded_name(0)
417 {
418 }
419
420 ps_font::~ps_font()
421 {
422   a_delete encoding;
423   a_delete reencoded_name;
424 }
425
426 void ps_font::handle_unknown_font_command(const char *command, const char *arg,
427                                           const char *filename, int lineno)
428 {
429   if (strcmp(command, "encoding") == 0) {
430     if (arg == 0)
431       error_with_file_and_line(filename, lineno,
432                                "`encoding' command requires an argument");
433     else
434       encoding = strsave(arg);
435   }
436 }
437
438 static void handle_unknown_desc_command(const char *command, const char *arg,
439                                         const char *filename, int lineno)
440 {
441   if (strcmp(command, "broken") == 0) {
442     if (arg == 0)
443       error_with_file_and_line(filename, lineno,
444                                "`broken' command requires an argument");
445     else if (!bflag)
446       broken_flags = atoi(arg);
447   }
448 }
449
450 struct style {
451   font *f;
452   int point_size;
453   int height;
454   int slant;
455   style();
456   style(font *, int, int, int);
457   int operator==(const style &) const;
458   int operator!=(const style &) const;
459 };
460
461 style::style() : f(0)
462 {
463 }
464
465 style::style(font *p, int sz, int h, int sl)
466 : f(p), point_size(sz), height(h), slant(sl)
467 {
468 }
469
470 int style::operator==(const style &s) const
471 {
472   return (f == s.f && point_size == s.point_size
473           && height == s.height && slant == s.slant);
474 }
475
476 int style::operator!=(const style &s) const
477 {
478   return !(*this == s);
479 }
480
481 class ps_printer : public printer {
482   FILE *tempfp;
483   ps_output out;
484   int res;
485   int space_char_index;
486   int pages_output;
487   int paper_length;
488   int equalise_spaces;
489   enum { SBUF_SIZE = 256 };
490   char sbuf[SBUF_SIZE];
491   int sbuf_len;
492   int sbuf_start_hpos;
493   int sbuf_vpos;
494   int sbuf_end_hpos;
495   int sbuf_space_width;
496   int sbuf_space_count;
497   int sbuf_space_diff_count;
498   int sbuf_space_code;
499   int sbuf_kern;
500   style sbuf_style;
501   color sbuf_color;             // the current PS color
502   style output_style;
503   int output_hpos;
504   int output_vpos;
505   int output_draw_point_size;
506   int line_thickness;
507   int output_line_thickness;
508   unsigned char output_space_code;
509   enum { MAX_DEFINED_STYLES = 50 };
510   style defined_styles[MAX_DEFINED_STYLES];
511   int ndefined_styles;
512   int next_encoding_index;
513   string defs;
514   int ndefs;
515   resource_manager rm;
516   int invis_count;
517
518   void flush_sbuf();
519   void set_style(const style &);
520   void set_space_code(unsigned char c);
521   int set_encoding_index(ps_font *);
522   void do_exec(char *, const environment *);
523   void do_import(char *, const environment *);
524   void do_def(char *, const environment *);
525   void do_mdef(char *, const environment *);
526   void do_file(char *, const environment *);
527   void do_invis(char *, const environment *);
528   void do_endinvis(char *, const environment *);
529   void set_line_thickness_and_color(const environment *);
530   void fill_path(const environment *);
531   void encode_fonts();
532   void define_encoding(const char *, int);
533   void reencode_font(ps_font *);
534   void set_color(color *c, int fill = 0);
535
536 public:
537   ps_printer(double);
538   ~ps_printer();
539   void set_char(int i, font *f, const environment *env, int w, const char *name);
540   void draw(int code, int *p, int np, const environment *env);
541   void begin_page(int);
542   void end_page(int);
543   void special(char *arg, const environment *env, char type);
544   font *make_font(const char *);
545   void end_of_line();
546 };
547
548 // `pl' is in inches
549 ps_printer::ps_printer(double pl)
550 : out(0, MAX_LINE_LENGTH),
551   pages_output(0),
552   sbuf_len(0),
553   output_hpos(-1),
554   output_vpos(-1),
555   line_thickness(-1),
556   ndefined_styles(0),
557   next_encoding_index(0),
558   ndefs(0),
559   invis_count(0)
560 {
561   tempfp = xtmpfile();
562   out.set_file(tempfp);
563   if (linewidth < 0)
564     linewidth = DEFAULT_LINEWIDTH;
565   if (font::hor != 1)
566     fatal("horizontal resolution must be 1");
567   if (font::vert != 1)
568     fatal("vertical resolution must be 1");
569   if (font::res % (font::sizescale*72) != 0)
570     fatal("res must be a multiple of 72*sizescale");
571   int r = font::res;
572   int point = 0;
573   while (r % 10 == 0) {
574     r /= 10;
575     point++;
576   }
577   res = r;
578   out.set_fixed_point(point);
579   space_char_index = font::name_to_index("space");
580   if (pl == 0)
581     paper_length = font::paperlength;
582   else
583     paper_length = int(pl * font::res + 0.5);
584   if (paper_length == 0)
585     paper_length = 11 * font::res;
586   equalise_spaces = font::res >= 72000;
587 }
588
589 int ps_printer::set_encoding_index(ps_font *f)
590 {
591   if (f->encoding_index >= 0)
592     return f->encoding_index;
593   for (font_pointer_list *p = font_list; p; p = p->next)
594     if (p->p != f) {
595       char *encoding = ((ps_font *)p->p)->encoding;
596       int encoding_index = ((ps_font *)p->p)->encoding_index;
597       if (encoding != 0 && encoding_index >= 0 
598           && strcmp(f->encoding, encoding) == 0) {
599         return f->encoding_index = encoding_index;
600       }
601     }
602   return f->encoding_index = next_encoding_index++;
603 }
604
605 void ps_printer::set_char(int i, font *f, const environment *env, int w, const char *name)
606 {
607   if (i == space_char_index || invis_count > 0)
608     return;
609   unsigned char code = f->get_code(i);
610   style sty(f, env->size, env->height, env->slant);
611   if (sty.slant != 0) {
612     if (sty.slant > 80 || sty.slant < -80) {
613       error("silly slant `%1' degrees", sty.slant);
614       sty.slant = 0;
615     }
616   }
617   if (sbuf_len > 0) {
618     if (sbuf_len < SBUF_SIZE
619         && sty == sbuf_style
620         && sbuf_vpos == env->vpos
621         && sbuf_color == *env->col) {
622       if (sbuf_end_hpos == env->hpos) {
623         sbuf[sbuf_len++] = code;
624         sbuf_end_hpos += w + sbuf_kern;
625         return;
626       }
627       if (sbuf_len == 1 && sbuf_kern == 0) {
628         sbuf_kern = env->hpos - sbuf_end_hpos;
629         sbuf_end_hpos = env->hpos + sbuf_kern + w;
630         sbuf[sbuf_len++] = code;
631         return;
632       }
633       /* If sbuf_end_hpos - sbuf_kern == env->hpos, we are better off
634          starting a new string. */
635       if (sbuf_len < SBUF_SIZE - 1 && env->hpos >= sbuf_end_hpos
636           && (sbuf_kern == 0 || sbuf_end_hpos - sbuf_kern != env->hpos)) {
637         if (sbuf_space_code < 0) {
638           if (f->contains(space_char_index)) {
639             sbuf_space_code = f->get_code(space_char_index);
640             sbuf_space_width = env->hpos - sbuf_end_hpos;
641             sbuf_end_hpos = env->hpos + w + sbuf_kern;
642             sbuf[sbuf_len++] = sbuf_space_code;
643             sbuf[sbuf_len++] = code;
644             sbuf_space_count++;
645             return;
646           }
647         }
648         else {
649           int diff = env->hpos - sbuf_end_hpos - sbuf_space_width;
650           if (diff == 0 || (equalise_spaces && (diff == 1 || diff == -1))) {
651             sbuf_end_hpos = env->hpos + w + sbuf_kern;
652             sbuf[sbuf_len++] = sbuf_space_code;
653             sbuf[sbuf_len++] = code;
654             sbuf_space_count++;
655             if (diff == 1)
656               sbuf_space_diff_count++;
657             else if (diff == -1)
658               sbuf_space_diff_count--;
659             return;
660           }
661         }
662       }
663     }
664     flush_sbuf();
665   }
666   sbuf_len = 1;
667   sbuf[0] = code;
668   sbuf_end_hpos = env->hpos + w;
669   sbuf_start_hpos = env->hpos;
670   sbuf_vpos = env->vpos;
671   sbuf_style = sty;
672   sbuf_space_code = -1;
673   sbuf_space_width = 0;
674   sbuf_space_count = sbuf_space_diff_count = 0;
675   sbuf_kern = 0;
676   if (sbuf_color != *env->col)
677     set_color(env->col);
678 }
679
680 static char *make_encoding_name(int encoding_index)
681 {
682   static char buf[3 + INT_DIGITS + 1];
683   sprintf(buf, "ENC%d", encoding_index);
684   return buf;
685 }
686
687 const char *const WS = " \t\n\r";
688
689 void ps_printer::define_encoding(const char *encoding, int encoding_index)
690 {
691   char *vec[256];
692   int i;
693   for (i = 0; i < 256; i++)
694     vec[i] = 0;
695   char *path;
696   FILE *fp = font::open_file(encoding, &path);
697   if (fp == 0)
698     fatal("can't open encoding file `%1'", encoding);
699   int lineno = 1;
700   char buf[256];
701   while (fgets(buf, 512, fp) != 0) {
702     char *p = buf;
703     while (csspace(*p))
704       p++;
705     if (*p != '#' && *p != '\0' && (p = strtok(buf, WS)) != 0) {
706       char *q = strtok(0, WS);
707       int n;
708       if (q == 0 || sscanf(q, "%d", &n) != 1 || n < 0 || n >= 256)
709         fatal_with_file_and_line(path, lineno, "bad second field");
710       vec[n] = new char[strlen(p) + 1];
711       strcpy(vec[n], p);
712     }
713     lineno++;
714   }
715   a_delete path;
716   out.put_literal_symbol(make_encoding_name(encoding_index));
717   out.put_delimiter('[');
718   for (i = 0; i < 256; i++) {
719     if (vec[i] == 0)
720       out.put_literal_symbol(".notdef");
721     else {
722       out.put_literal_symbol(vec[i]);
723       a_delete vec[i];
724     }
725   }
726   out.put_delimiter(']')
727      .put_symbol("def");
728 }
729
730 void ps_printer::reencode_font(ps_font *f)
731 {
732   out.put_literal_symbol(f->reencoded_name)
733      .put_symbol(make_encoding_name(f->encoding_index))
734      .put_literal_symbol(f->get_internal_name())
735      .put_symbol("RE");
736 }
737
738 void ps_printer::encode_fonts()
739 {
740   if (next_encoding_index == 0)
741     return;
742   char *done_encoding = new char[next_encoding_index];
743   for (int i = 0; i < next_encoding_index; i++)
744     done_encoding[i] = 0;
745   for (font_pointer_list *f = font_list; f; f = f->next) {
746     int encoding_index = ((ps_font *)f->p)->encoding_index;
747     if (encoding_index >= 0) {
748       assert(encoding_index < next_encoding_index);
749       if (!done_encoding[encoding_index]) {
750         done_encoding[encoding_index] = 1;
751         define_encoding(((ps_font *)f->p)->encoding, encoding_index);
752       }
753       reencode_font((ps_font *)f->p);
754     }
755   }
756   a_delete done_encoding;
757 }
758
759 void ps_printer::set_style(const style &sty)
760 {
761   char buf[1 + INT_DIGITS + 1];
762   for (int i = 0; i < ndefined_styles; i++)
763     if (sty == defined_styles[i]) {
764       sprintf(buf, "F%d", i);
765       out.put_symbol(buf);
766       return;
767     }
768   if (ndefined_styles >= MAX_DEFINED_STYLES)
769     ndefined_styles = 0;
770   sprintf(buf, "F%d", ndefined_styles);
771   out.put_literal_symbol(buf);
772   const char *psname = sty.f->get_internal_name();
773   if (psname == 0)
774     fatal("no internalname specified for font `%1'", sty.f->get_name());
775   char *encoding = ((ps_font *)sty.f)->encoding;
776   if (encoding != 0) {
777     char *s = ((ps_font *)sty.f)->reencoded_name;
778     if (s == 0) {
779       int ei = set_encoding_index((ps_font *)sty.f);
780       char *tem = new char[strlen(psname) + 1 + INT_DIGITS + 1];
781       sprintf(tem, "%s@%d", psname, ei);
782       psname = tem;
783       ((ps_font *)sty.f)->reencoded_name = tem;
784     }
785     else
786       psname = s;
787   }
788   out.put_fix_number((font::res/(72*font::sizescale))*sty.point_size);
789   if (sty.height != 0 || sty.slant != 0) {
790     int h = sty.height == 0 ? sty.point_size : sty.height;
791     h *= font::res/(72*font::sizescale);
792     int c = int(h*tan(radians(sty.slant)) + .5);
793     out.put_fix_number(c)
794        .put_fix_number(h)
795        .put_literal_symbol(psname)
796        .put_symbol("MF");
797   }
798   else {
799     out.put_literal_symbol(psname)
800        .put_symbol("SF");
801   }
802   defined_styles[ndefined_styles++] = sty;
803 }
804
805 void ps_printer::set_color(color *col, int fill)
806 {
807   sbuf_color = *col;
808   unsigned int components[4];
809   char s[3];
810   color_scheme cs = col->get_components(components);
811   s[0] = fill ? 'F' : 'C';
812   s[2] = 0;
813   switch (cs) {
814   case DEFAULT:                 // black
815     out.put_symbol("0");
816     s[1] = 'g';
817     break;
818   case RGB:
819     out.put_color(Red)
820        .put_color(Green)
821        .put_color(Blue);
822     s[1] = 'r';
823     break;
824   case CMY:
825     col->get_cmyk(&Cyan, &Magenta, &Yellow, &Black);
826     // fall through
827   case CMYK:
828     out.put_color(Cyan)
829        .put_color(Magenta)
830        .put_color(Yellow)
831        .put_color(Black);
832     s[1] = 'k';
833     break;
834   case GRAY:
835     out.put_color(Gray);
836     s[1] = 'g';
837     break;
838   }
839   out.put_symbol(s);
840 }
841
842 void ps_printer::set_space_code(unsigned char c)
843 {
844   out.put_literal_symbol("SC")
845      .put_number(c)
846      .put_symbol("def");
847 }
848
849 void ps_printer::end_of_line()
850 {
851   flush_sbuf();
852   // this ensures that we do an absolute motion to the beginning of a line
853   output_vpos = output_hpos = -1;
854 }
855
856 void ps_printer::flush_sbuf()
857 {
858   enum {
859     NONE,
860     RELATIVE_H,
861     RELATIVE_V,
862     RELATIVE_HV,
863     ABSOLUTE
864     } motion = NONE;
865   int space_flag = 0;
866   if (sbuf_len == 0)
867     return;
868   if (output_style != sbuf_style) {
869     set_style(sbuf_style);
870     output_style = sbuf_style;
871   }
872   int extra_space = 0;
873   if (output_hpos < 0 || output_vpos < 0)
874     motion = ABSOLUTE;
875   else {
876     if (output_hpos != sbuf_start_hpos)
877       motion = RELATIVE_H;
878     if (output_vpos != sbuf_vpos) {
879       if  (motion != NONE)
880         motion = RELATIVE_HV;
881       else
882         motion = RELATIVE_V;
883     }
884   }
885   if (sbuf_space_code >= 0) {
886     int w = sbuf_style.f->get_width(space_char_index, sbuf_style.point_size);
887     if (w + sbuf_kern != sbuf_space_width) {
888       if (sbuf_space_code != output_space_code) {
889         set_space_code(sbuf_space_code);
890         output_space_code = sbuf_space_code;
891       }
892       space_flag = 1;
893       extra_space = sbuf_space_width - w - sbuf_kern;
894       if (sbuf_space_diff_count > sbuf_space_count/2)
895         extra_space++;
896       else if (sbuf_space_diff_count < -(sbuf_space_count/2))
897         extra_space--;
898     }
899   }
900   if (space_flag)
901     out.put_fix_number(extra_space);
902   if (sbuf_kern != 0)
903     out.put_fix_number(sbuf_kern);
904   out.put_string(sbuf, sbuf_len);
905   char command_array[] = {'A', 'B', 'C', 'D',
906                           'E', 'F', 'G', 'H',
907                           'I', 'J', 'K', 'L',
908                           'M', 'N', 'O', 'P',
909                           'Q', 'R', 'S', 'T'};
910   char sym[2];
911   sym[0] = command_array[motion*4 + space_flag + 2*(sbuf_kern != 0)];
912   sym[1] = '\0';
913   switch (motion) {
914   case NONE:
915     break;
916   case ABSOLUTE:
917     out.put_fix_number(sbuf_start_hpos)
918        .put_fix_number(sbuf_vpos);
919     break;
920   case RELATIVE_H:
921     out.put_fix_number(sbuf_start_hpos - output_hpos);
922     break;
923   case RELATIVE_V:
924     out.put_fix_number(sbuf_vpos - output_vpos);
925     break;
926   case RELATIVE_HV:
927     out.put_fix_number(sbuf_start_hpos - output_hpos)
928        .put_fix_number(sbuf_vpos - output_vpos);
929     break;
930   default:
931     assert(0);
932   }
933   out.put_symbol(sym);
934   output_hpos = sbuf_end_hpos;
935   output_vpos = sbuf_vpos;
936   sbuf_len = 0;
937 }
938
939 void ps_printer::set_line_thickness_and_color(const environment *env)
940 {
941   if (line_thickness < 0) {
942     if (output_draw_point_size != env->size) {
943       // we ought to check for overflow here
944       int lw = ((font::res/(72*font::sizescale))*linewidth*env->size)/1000;
945       out.put_fix_number(lw)
946          .put_symbol("LW");
947       output_draw_point_size = env->size;
948       output_line_thickness = -1;
949     }
950   }
951   else {
952     if (output_line_thickness != line_thickness) {
953       out.put_fix_number(line_thickness)
954          .put_symbol("LW");
955       output_line_thickness = line_thickness;
956       output_draw_point_size = -1;
957     }
958   }
959   if (sbuf_color != *env->col)
960     set_color(env->col);
961 }
962
963 void ps_printer::fill_path(const environment *env)
964 {
965   if (sbuf_color == *env->fill)
966     out.put_symbol("FL");
967   else
968     set_color(env->fill, 1);
969 }
970
971 void ps_printer::draw(int code, int *p, int np, const environment *env)
972 {
973   if (invis_count > 0)
974     return;
975   flush_sbuf();
976   int fill_flag = 0;
977   switch (code) {
978   case 'C':
979     fill_flag = 1;
980     // fall through
981   case 'c':
982     // troff adds an extra argument to C
983     if (np != 1 && !(code == 'C' && np == 2)) {
984       error("1 argument required for circle");
985       break;
986     }
987     out.put_fix_number(env->hpos + p[0]/2)
988        .put_fix_number(env->vpos)
989        .put_fix_number(p[0]/2)
990        .put_symbol("DC");
991     if (fill_flag)
992       fill_path(env);
993     else {
994       set_line_thickness_and_color(env);
995       out.put_symbol("ST");
996     }
997     break;
998   case 'l':
999     if (np != 2) {
1000       error("2 arguments required for line");
1001       break;
1002     }
1003     set_line_thickness_and_color(env);
1004     out.put_fix_number(p[0] + env->hpos)
1005        .put_fix_number(p[1] + env->vpos)
1006        .put_fix_number(env->hpos)
1007        .put_fix_number(env->vpos)
1008        .put_symbol("DL");
1009     break;
1010   case 'E':
1011     fill_flag = 1;
1012     // fall through
1013   case 'e':
1014     if (np != 2) {
1015       error("2 arguments required for ellipse");
1016       break;
1017     }
1018     out.put_fix_number(p[0])
1019        .put_fix_number(p[1])
1020        .put_fix_number(env->hpos + p[0]/2)
1021        .put_fix_number(env->vpos)
1022        .put_symbol("DE");
1023     if (fill_flag)
1024       fill_path(env);
1025     else {
1026       set_line_thickness_and_color(env);
1027       out.put_symbol("ST");
1028     }
1029     break;
1030   case 'P':
1031     fill_flag = 1;
1032     // fall through
1033   case 'p':
1034     {
1035       if (np & 1) {
1036         error("even number of arguments required for polygon");
1037         break;
1038       }
1039       if (np == 0) {
1040         error("no arguments for polygon");
1041         break;
1042       }
1043       out.put_fix_number(env->hpos)
1044          .put_fix_number(env->vpos)
1045          .put_symbol("MT");
1046       for (int i = 0; i < np; i += 2)
1047         out.put_fix_number(p[i])
1048            .put_fix_number(p[i+1])
1049            .put_symbol("RL");
1050       out.put_symbol("CL");
1051       if (fill_flag)
1052         fill_path(env);
1053       else {
1054         set_line_thickness_and_color(env);
1055         out.put_symbol("ST");
1056       }
1057       break;
1058     }
1059   case '~':
1060     {
1061       if (np & 1) {
1062         error("even number of arguments required for spline");
1063         break;
1064       }
1065       if (np == 0) {
1066         error("no arguments for spline");
1067         break;
1068       }
1069       out.put_fix_number(env->hpos)
1070          .put_fix_number(env->vpos)
1071          .put_symbol("MT");
1072       out.put_fix_number(p[0]/2)
1073          .put_fix_number(p[1]/2)
1074          .put_symbol("RL");
1075       /* tnum/tden should be between 0 and 1; the closer it is to 1
1076          the tighter the curve will be to the guiding lines; 2/3
1077          is the standard value */
1078       const int tnum = 2;
1079       const int tden = 3;
1080       for (int i = 0; i < np - 2; i += 2) {
1081         out.put_fix_number((p[i]*tnum)/(2*tden))
1082            .put_fix_number((p[i + 1]*tnum)/(2*tden))
1083            .put_fix_number(p[i]/2 + (p[i + 2]*(tden - tnum))/(2*tden))
1084            .put_fix_number(p[i + 1]/2 + (p[i + 3]*(tden - tnum))/(2*tden))
1085            .put_fix_number((p[i] - p[i]/2) + p[i + 2]/2)
1086            .put_fix_number((p[i + 1] - p[i + 1]/2) + p[i + 3]/2)
1087            .put_symbol("RC");
1088       }
1089       out.put_fix_number(p[np - 2] - p[np - 2]/2)
1090          .put_fix_number(p[np - 1] - p[np - 1]/2)
1091          .put_symbol("RL");
1092       set_line_thickness_and_color(env);
1093       out.put_symbol("ST");
1094     }
1095     break;
1096   case 'a':
1097     {
1098       if (np != 4) {
1099         error("4 arguments required for arc");
1100         break;
1101       }
1102       set_line_thickness_and_color(env);
1103       double c[2];
1104       if (adjust_arc_center(p, c))
1105         out.put_fix_number(env->hpos + int(c[0]))
1106            .put_fix_number(env->vpos + int(c[1]))
1107            .put_fix_number(int(sqrt(c[0]*c[0] + c[1]*c[1])))
1108            .put_float(degrees(atan2(-c[1], -c[0])))
1109            .put_float(degrees(atan2(p[1] + p[3] - c[1], p[0] + p[2] - c[0])))
1110            .put_symbol("DA");
1111       else
1112         out.put_fix_number(p[0] + p[2] + env->hpos)
1113            .put_fix_number(p[1] + p[3] + env->vpos)
1114            .put_fix_number(env->hpos)
1115            .put_fix_number(env->vpos)
1116            .put_symbol("DL");
1117     }
1118     break;
1119   case 't':
1120     if (np == 0)
1121       line_thickness = -1;
1122     else {
1123       // troff gratuitously adds an extra 0
1124       if (np != 1 && np != 2) {
1125         error("0 or 1 argument required for thickness");
1126         break;
1127       }
1128       line_thickness = p[0];
1129     }
1130     break;
1131   default:
1132     error("unrecognised drawing command `%1'", char(code));
1133     break;
1134   }
1135   output_hpos = output_vpos = -1;
1136 }
1137
1138 void ps_printer::begin_page(int n)
1139 {
1140   out.begin_comment("Page:")
1141      .comment_arg(i_to_a(n));
1142   out.comment_arg(i_to_a(++pages_output))
1143      .end_comment();
1144   output_style.f = 0;
1145   output_space_code = 32;
1146   output_draw_point_size = -1;
1147   output_line_thickness = -1;
1148   output_hpos = output_vpos = -1;
1149   ndefined_styles = 0;
1150   out.simple_comment("BeginPageSetup")
1151      .put_symbol("BP")
1152      .simple_comment("EndPageSetup");
1153   if (sbuf_color != default_color)
1154     set_color(&sbuf_color);
1155 }
1156
1157 void ps_printer::end_page(int)
1158 {
1159   flush_sbuf();
1160   set_color(&default_color);
1161   out.put_symbol("EP");
1162   if (invis_count != 0) {
1163     error("missing `endinvis' command");
1164     invis_count = 0;
1165   }
1166 }
1167
1168 font *ps_printer::make_font(const char *nm)
1169 {
1170   return ps_font::load_ps_font(nm);
1171 }
1172
1173 ps_printer::~ps_printer()
1174 {
1175   out.simple_comment("Trailer")
1176      .put_symbol("end")
1177      .simple_comment("EOF");
1178   if (fseek(tempfp, 0L, 0) < 0)
1179     fatal("fseek on temporary file failed");
1180   fputs("%!PS-Adobe-", stdout);
1181   fputs((broken_flags & USE_PS_ADOBE_2_0) ? "2.0" : "3.0", stdout);
1182   putchar('\n');
1183   out.set_file(stdout);
1184   {
1185     out.begin_comment("Creator:")
1186        .comment_arg("groff")
1187        .comment_arg("version")
1188        .comment_arg(Version_string)
1189        .end_comment();
1190   }
1191   {
1192     fputs("%%CreationDate: ", out.get_file());
1193 #ifdef LONG_FOR_TIME_T
1194     long
1195 #else
1196     time_t
1197 #endif
1198     t = time(0);
1199     fputs(ctime(&t), out.get_file());
1200   }
1201   for (font_pointer_list *f = font_list; f; f = f->next) {
1202     ps_font *psf = (ps_font *)(f->p);
1203     rm.need_font(psf->get_internal_name());
1204   }
1205   rm.print_header_comments(out);
1206   out.begin_comment("Pages:")
1207      .comment_arg(i_to_a(pages_output))
1208      .end_comment();
1209   out.begin_comment("PageOrder:")
1210      .comment_arg("Ascend")
1211      .end_comment();
1212 #if 0
1213   fprintf(out.get_file(), "%%%%DocumentMedia: () %g %g 0 () ()\n",
1214           font::paperwidth*72.0/font::res,
1215           paper_length*72.0/font::res);
1216 #endif
1217   out.begin_comment("Orientation:")
1218      .comment_arg(landscape_flag ? "Landscape" : "Portrait")
1219      .end_comment(); 
1220   if (ncopies != 1) {
1221     out.end_line();
1222     fprintf(out.get_file(), "%%%%Requirements: numcopies(%d)\n", ncopies);
1223   }
1224   out.simple_comment("EndComments");
1225   out.simple_comment("BeginProlog");
1226   rm.output_prolog(out);
1227   if (!(broken_flags & NO_SETUP_SECTION)) {
1228     out.simple_comment("EndProlog");
1229     out.simple_comment("BeginSetup");
1230   }
1231   rm.document_setup(out);
1232   out.put_symbol(dict_name)
1233      .put_symbol("begin");
1234   if (ndefs > 0)
1235     ndefs += DEFS_DICT_SPARE;
1236   out.put_literal_symbol(defs_dict_name)
1237      .put_number(ndefs + 1)
1238      .put_symbol("dict")
1239      .put_symbol("def");
1240   out.put_symbol(defs_dict_name)
1241      .put_symbol("begin");
1242   out.put_literal_symbol("u")
1243      .put_delimiter('{')
1244      .put_fix_number(1)
1245      .put_symbol("mul")
1246      .put_delimiter('}')
1247      .put_symbol("bind")
1248      .put_symbol("def");
1249   defs += '\0';
1250   out.special(defs.contents());
1251   out.put_symbol("end");
1252   if (ncopies != 1)
1253     out.put_literal_symbol("#copies")
1254        .put_number(ncopies)
1255        .put_symbol("def");
1256   out.put_literal_symbol("RES")
1257      .put_number(res)
1258      .put_symbol("def");
1259   out.put_literal_symbol("PL");
1260   if (guess_flag)
1261     out.put_symbol("PLG");
1262   else
1263     out.put_fix_number(paper_length);
1264   out.put_symbol("def");
1265   out.put_literal_symbol("LS")
1266      .put_symbol(landscape_flag ? "true" : "false")
1267      .put_symbol("def");
1268   if (manual_feed_flag) {
1269     out.begin_comment("BeginFeature:")
1270        .comment_arg("*ManualFeed")
1271        .comment_arg("True")
1272        .end_comment()
1273        .put_symbol("MANUAL")
1274        .simple_comment("EndFeature");
1275   }
1276   encode_fonts();
1277   out.simple_comment((broken_flags & NO_SETUP_SECTION)
1278                      ? "EndProlog"
1279                      : "EndSetup");
1280   out.end_line();
1281   out.copy_file(tempfp);
1282   fclose(tempfp);
1283 }
1284
1285 void ps_printer::special(char *arg, const environment *env, char type)
1286 {
1287   if (type != 'p')
1288     return;
1289   typedef void (ps_printer::*SPECIAL_PROCP)(char *, const environment *);
1290   static struct {
1291     const char *name;
1292     SPECIAL_PROCP proc;
1293   } proc_table[] = {
1294     { "exec", &ps_printer::do_exec },
1295     { "def", &ps_printer::do_def },
1296     { "mdef", &ps_printer::do_mdef },
1297     { "import", &ps_printer::do_import },
1298     { "file", &ps_printer::do_file },
1299     { "invis", &ps_printer::do_invis },
1300     { "endinvis", &ps_printer::do_endinvis },
1301   };
1302   char *p;
1303   for (p = arg; *p == ' ' || *p == '\n'; p++)
1304     ;
1305   char *tag = p;
1306   for (; *p != '\0' && *p != ':' && *p != ' ' && *p != '\n'; p++)
1307     ;
1308   if (*p == '\0' || strncmp(tag, "ps", p - tag) != 0) {
1309     error("X command without `ps:' tag ignored");
1310     return;
1311   }
1312   p++;
1313   for (; *p == ' ' || *p == '\n'; p++)
1314     ;
1315   char *command = p;
1316   for (; *p != '\0' && *p != ' ' && *p != '\n'; p++)
1317     ;
1318   if (*command == '\0') {
1319     error("empty X command ignored");
1320     return;
1321   }
1322   for (unsigned int i = 0; i < sizeof(proc_table)/sizeof(proc_table[0]); i++)
1323     if (strncmp(command, proc_table[i].name, p - command) == 0) {
1324       (this->*(proc_table[i].proc))(p, env);
1325       return;
1326     }
1327   error("X command `%1' not recognised", command);
1328 }
1329
1330 // A conforming PostScript document must not have lines longer
1331 // than 255 characters (excluding line termination characters).
1332
1333 static int check_line_lengths(const char *p)
1334 {
1335   for (;;) {
1336     const char *end = strchr(p, '\n');
1337     if (end == 0)
1338       end = strchr(p, '\0');
1339     if (end - p > 255)
1340       return 0;
1341     if (*end == '\0')
1342       break;
1343     p = end + 1;
1344   }
1345   return 1;
1346 }
1347
1348 void ps_printer::do_exec(char *arg, const environment *env)
1349 {
1350   flush_sbuf();
1351   while (csspace(*arg))
1352     arg++;
1353   if (*arg == '\0') {
1354     error("missing argument to X exec command");
1355     return;
1356   }
1357   if (!check_line_lengths(arg)) {
1358     error("lines in X exec command must not be more than 255 characters long");
1359     return;
1360   }
1361   out.put_fix_number(env->hpos)
1362      .put_fix_number(env->vpos)
1363      .put_symbol("EBEGIN")
1364      .special(arg)
1365      .put_symbol("EEND");
1366   output_hpos = output_vpos = -1;
1367   output_style.f = 0;
1368   output_draw_point_size = -1;
1369   output_line_thickness = -1;
1370   ndefined_styles = 0;
1371   if (!ndefs)
1372     ndefs = 1;
1373 }
1374
1375 void ps_printer::do_file(char *arg, const environment *env)
1376 {
1377   flush_sbuf();
1378   while (csspace(*arg))
1379     arg++;
1380   if (*arg == '\0') {
1381     error("missing argument to X file command");
1382     return;
1383   }
1384   const char *filename = arg;
1385   do {
1386     ++arg;
1387   } while (*arg != '\0' && *arg != ' ' && *arg != '\n');
1388   out.put_fix_number(env->hpos)
1389      .put_fix_number(env->vpos)
1390      .put_symbol("EBEGIN");
1391   rm.import_file(filename, out);
1392   out.put_symbol("EEND");
1393   output_hpos = output_vpos = -1;
1394   output_style.f = 0;
1395   output_draw_point_size = -1;
1396   output_line_thickness = -1;
1397   ndefined_styles = 0;
1398   if (!ndefs)
1399     ndefs = 1;
1400 }
1401
1402 void ps_printer::do_def(char *arg, const environment *)
1403 {
1404   flush_sbuf();
1405   while (csspace(*arg))
1406     arg++;
1407   if (!check_line_lengths(arg)) {
1408     error("lines in X def command must not be more than 255 characters long");
1409     return;
1410   }
1411   defs += arg;
1412   if (*arg != '\0' && strchr(arg, '\0')[-1] != '\n')
1413     defs += '\n';
1414   ndefs++;
1415 }
1416
1417 // Like def, but the first argument says how many definitions it contains.
1418
1419 void ps_printer::do_mdef(char *arg, const environment *)
1420 {
1421   flush_sbuf();
1422   char *p;
1423   int n = (int)strtol(arg, &p, 10);
1424   if (n == 0 && p == arg) {
1425     error("first argument to X mdef must be an integer");
1426     return;
1427   }
1428   if (n < 0) {
1429     error("out of range argument `%1' to X mdef command", int(n));
1430     return;
1431   }
1432   arg = p;
1433   while (csspace(*arg))
1434     arg++;
1435   if (!check_line_lengths(arg)) {
1436     error("lines in X mdef command must not be more than 255 characters long");
1437     return;
1438   }
1439   defs += arg;
1440   if (*arg != '\0' && strchr(arg, '\0')[-1] != '\n')
1441     defs += '\n';
1442   ndefs += n;
1443 }
1444
1445 void ps_printer::do_import(char *arg, const environment *env)
1446 {
1447   flush_sbuf();
1448   while (*arg == ' ' || *arg == '\n')
1449     arg++;
1450   char *p;
1451   for (p = arg; *p != '\0' && *p != ' ' && *p != '\n'; p++)
1452     ;
1453   if (*p != '\0')
1454     *p++ = '\0';
1455   int parms[6];
1456   int nparms = 0;
1457   while (nparms < 6) {
1458     char *end;
1459     long n = strtol(p, &end, 10);
1460     if (n == 0 && end == p)
1461       break;
1462     parms[nparms++] = int(n);
1463     p = end;
1464   }
1465   if (csalpha(*p) && (p[1] == '\0' || p[1] == ' ' || p[1] == '\n')) {
1466     error("scaling indicators not allowed in arguments for X import command");
1467     return;
1468   }
1469   while (*p == ' ' || *p == '\n')
1470     p++;
1471   if (nparms < 5) {
1472     if (*p == '\0')
1473       error("too few arguments for X import command");
1474     else
1475       error("invalid argument `%1' for X import command", p);
1476     return;
1477   }
1478   if (*p != '\0') {
1479     error("superflous argument `%1' for X import command", p);
1480     return;
1481   }
1482   int llx = parms[0];
1483   int lly = parms[1];
1484   int urx = parms[2];
1485   int ury = parms[3];
1486   int desired_width = parms[4];
1487   int desired_height = parms[5];
1488   if (desired_width <= 0) {
1489     error("bad width argument `%1' for X import command: must be > 0",
1490           desired_width);
1491     return;
1492   }
1493   if (nparms == 6 && desired_height <= 0) {
1494     error("bad height argument `%1' for X import command: must be > 0",
1495           desired_height);
1496     return;
1497   }
1498   if (llx == urx) {
1499     error("llx and urx arguments for X import command must not be equal");
1500     return;
1501   }
1502   if (lly == ury) {
1503     error("lly and ury arguments for X import command must not be equal");
1504     return;
1505   }
1506   if (nparms == 5) {
1507     int old_wid = urx - llx;
1508     int old_ht = ury - lly;
1509     if (old_wid < 0)
1510       old_wid = -old_wid;
1511     if (old_ht < 0)
1512       old_ht = -old_ht;
1513     desired_height = int(desired_width*(double(old_ht)/double(old_wid)) + .5);
1514   }
1515   if (env->vpos - desired_height < 0)
1516     warning("top of imported graphic is above the top of the page");
1517   out.put_number(llx)
1518      .put_number(lly)
1519      .put_fix_number(desired_width)
1520      .put_number(urx - llx)
1521      .put_fix_number(-desired_height)
1522      .put_number(ury - lly)
1523      .put_fix_number(env->hpos)
1524      .put_fix_number(env->vpos)
1525      .put_symbol("PBEGIN");
1526   rm.import_file(arg, out);
1527   // do this here just in case application defines PEND
1528   out.put_symbol("end");
1529   out.put_symbol("PEND");
1530 }
1531
1532 void ps_printer::do_invis(char *, const environment *)
1533 {
1534   invis_count++;
1535 }
1536
1537 void ps_printer::do_endinvis(char *, const environment *)
1538 {
1539   if (invis_count == 0)
1540     error("unbalanced `endinvis' command");
1541   else
1542     --invis_count;
1543 }
1544
1545 printer *make_printer()
1546 {
1547   return new ps_printer(user_paper_length);
1548 }
1549
1550 static void usage(FILE *stream);
1551
1552 int main(int argc, char **argv)
1553 {
1554   program_name = argv[0];
1555   string env;
1556   static char stderr_buf[BUFSIZ];
1557   setbuf(stderr, stderr_buf);
1558   int c;
1559   static const struct option long_options[] = {
1560     { "help", no_argument, 0, CHAR_MAX + 1 },
1561     { "version", no_argument, 0, 'v' },
1562     { NULL, 0, 0, 0 }
1563   };
1564   while ((c = getopt_long(argc, argv, "b:c:F:glmp:P:vw:", long_options, NULL))
1565          != EOF)
1566     switch(c) {
1567     case 'b':
1568       // XXX check this
1569       broken_flags = atoi(optarg);
1570       bflag = 1;
1571       break;
1572     case 'c':
1573       if (sscanf(optarg, "%d", &ncopies) != 1 || ncopies <= 0) {
1574         error("bad number of copies `%s'", optarg);
1575         ncopies = 1;
1576       }
1577       break;
1578     case 'F':
1579       font::command_line_font_dir(optarg);
1580       break;
1581     case 'g':
1582       guess_flag = 1;
1583       break;
1584     case 'l':
1585       landscape_flag = 1;
1586       break;
1587     case 'm':
1588       manual_feed_flag = 1;
1589       break;
1590     case 'p':
1591       if (!font::scan_papersize(optarg, 0, &user_paper_length, 0))
1592         error("invalid custom paper size `%1' ignored", optarg);
1593       break;
1594     case 'P':
1595       env = "GROPS_PROLOGUE";
1596       env += '=';
1597       env += optarg;
1598       env += '\0';
1599       if (putenv(strsave(env.contents())))
1600         fatal("putenv failed");
1601       break;
1602     case 'v':
1603       printf("GNU grops (groff) version %s\n", Version_string);
1604       exit(0);
1605       break;
1606     case 'w':
1607       if (sscanf(optarg, "%d", &linewidth) != 1 || linewidth < 0) {
1608         error("bad linewidth `%1'", optarg);
1609         linewidth = -1;
1610       }
1611       break;
1612     case CHAR_MAX + 1: // --help
1613       usage(stdout);
1614       exit(0);
1615       break;
1616     case '?':
1617       usage(stderr);
1618       exit(1);
1619       break;
1620     default:
1621       assert(0);
1622     }
1623   font::set_unknown_desc_command_handler(handle_unknown_desc_command);
1624 #ifdef SET_BINARY
1625   SET_BINARY(fileno(stdout));
1626 #endif
1627   if (optind >= argc)
1628     do_file("-");
1629   else {
1630     for (int i = optind; i < argc; i++)
1631       do_file(argv[i]);
1632   }
1633   delete pr;
1634   return 0;
1635 }
1636
1637 static void usage(FILE *stream)
1638 {
1639   fprintf(stream,
1640     "usage: %s [-glmv] [-b n] [-c n] [-w n] [-P prologue] [-F dir] [files ...]\n",
1641     program_name);
1642 }