Update to groff 1.19.2.
[dragonfly.git] / contrib / groff-1.19 / src / devices / grolj4 / lj4.cpp
1 // -*- C++ -*-
2 /* Copyright (C) 1994, 2000, 2001, 2002, 2003, 2004
3    Free Software Foundation, Inc.
4      Written by James Clark (jjc@jclark.com)
5
6 This file is part of groff.
7
8 groff is free software; you can redistribute it and/or modify it under
9 the terms of the GNU General Public License as published by the Free
10 Software Foundation; either version 2, or (at your option) any later
11 version.
12
13 groff is distributed in the hope that it will be useful, but WITHOUT ANY
14 WARRANTY; without even the implied warranty of MERCHANTABILITY or
15 FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
16 for more details.
17
18 You should have received a copy of the GNU General Public License along
19 with groff; see the file COPYING.  If not, write to the Free Software
20 Foundation, 51 Franklin St - Fifth Floor, Boston, MA 02110-1301, USA. */
21
22 /*
23 TODO
24
25 option to use beziers for circle/ellipse/arc
26 option to use lines for spline (for LJ3)
27 left/top offset registration
28 output bin selection option
29 paper source option
30 output non-integer parameters using fixed point numbers
31 X command to insert contents of file
32 X command to specify inline escape sequence (how to specify unprintable chars?)
33 X command to include bitmap graphics
34 */
35
36 #include "driver.h"
37 #include "nonposix.h"
38
39 extern "C" const char *Version_string;
40
41 static struct {
42   const char *name;
43   int code;
44   // at 300dpi
45   int x_offset_portrait;
46   int x_offset_landscape;
47 } paper_table[] = {
48   { "letter", 2, 75, 60 },
49   { "legal", 3, 75, 60 },
50   { "executive", 1, 75, 60 },
51   { "a4", 26, 71, 59 },
52   { "com10", 81, 75, 60 },
53   { "monarch", 80, 75, 60 },
54   { "c5", 91, 71, 59 },
55   { "b5", 100, 71, 59 },
56   { "dl", 90, 71, 59 },
57 };
58
59 static int user_paper_size = -1;
60 static int landscape_flag = 0;
61 static int duplex_flag = 0;
62
63 // An upper limit on the paper size in centipoints,
64 // used for setting HPGL picture frame.
65 #define MAX_PAPER_WIDTH (12*720)
66 #define MAX_PAPER_HEIGHT (17*720)
67
68 // Dotted lines that are thinner than this don't work right.
69 #define MIN_DOT_PEN_WIDTH .351
70
71 #ifndef DEFAULT_LINE_WIDTH_FACTOR
72 // in ems/1000
73 #define DEFAULT_LINE_WIDTH_FACTOR 40
74 #endif
75
76 const int DEFAULT_HPGL_UNITS = 1016;
77 int line_width_factor = DEFAULT_LINE_WIDTH_FACTOR;
78 unsigned ncopies = 0;           // 0 means don't send ncopies command
79
80 static int lookup_paper_size(const char *);
81
82 class lj4_font : public font {
83 public:
84   ~lj4_font();
85   void handle_unknown_font_command(const char *command, const char *arg,
86                                    const char *filename, int lineno);
87   static lj4_font *load_lj4_font(const char *);
88   int weight;
89   int style;
90   int proportional;
91   int typeface;
92 private:
93   lj4_font(const char *);
94 };
95
96 lj4_font::lj4_font(const char *nm)
97 : font(nm), weight(0), style(0), proportional(0), typeface(0)
98 {
99 }
100
101 lj4_font::~lj4_font()
102 {
103 }
104
105 lj4_font *lj4_font::load_lj4_font(const char *s)
106 {
107   lj4_font *f = new lj4_font(s);
108   if (!f->load()) {
109     delete f;
110     return 0;
111   }
112   return f;
113 }
114
115 static struct {
116   const char *s;
117   int lj4_font::*ptr;
118   int min;
119   int max;
120 } command_table[] = {
121   { "pclweight", &lj4_font::weight, -7, 7 },
122   { "pclstyle", &lj4_font::style, 0, 32767 },
123   { "pclproportional", &lj4_font::proportional, 0, 1 },
124   { "pcltypeface", &lj4_font::typeface, 0, 65535 },
125 };
126
127 void lj4_font::handle_unknown_font_command(const char *command,
128                                            const char *arg,
129                                            const char *filename, int lineno)
130 {
131   for (unsigned int i = 0;
132        i < sizeof(command_table)/sizeof(command_table[0]); i++) {
133     if (strcmp(command, command_table[i].s) == 0) {
134       if (arg == 0)
135         fatal_with_file_and_line(filename, lineno,
136                                  "`%1' command requires an argument",
137                                  command);
138       char *ptr;
139       long n = strtol(arg, &ptr, 10);
140       if (n == 0 && ptr == arg)
141         fatal_with_file_and_line(filename, lineno,
142                                  "`%1' command requires numeric argument",
143                                  command);
144       if (n < command_table[i].min) {
145         error_with_file_and_line(filename, lineno,
146                                  "argument for `%1' command must not be less than %2",
147                                  command, command_table[i].min);
148         n = command_table[i].min;
149       }
150       else if (n > command_table[i].max) {
151         error_with_file_and_line(filename, lineno,
152                                  "argument for `%1' command must not be greater than %2",
153                                  command, command_table[i].max);
154         n = command_table[i].max;
155       }
156       this->*command_table[i].ptr = int(n);
157       break;
158     }
159   }
160 }
161
162 class lj4_printer : public printer {
163 public:
164   lj4_printer(int);
165   ~lj4_printer();
166   void set_char(int, font *, const environment *, int, const char *name);
167   void draw(int code, int *p, int np, const environment *env);
168   void begin_page(int);
169   void end_page(int page_length);
170   font *make_font(const char *);
171   void end_of_line();
172 private:
173   void set_line_thickness(int size, int dot = 0);
174   void hpgl_init();
175   void hpgl_start();
176   void hpgl_end();
177   int moveto(int hpos, int vpos);
178   int moveto1(int hpos, int vpos);
179
180   int cur_hpos;
181   int cur_vpos;
182   lj4_font *cur_font;
183   int cur_size;
184   unsigned short cur_symbol_set;
185   int x_offset;
186   int line_thickness;
187   double pen_width;
188   double hpgl_scale;
189   int hpgl_inited;
190   int paper_size;
191 };
192
193 inline
194 int lj4_printer::moveto(int hpos, int vpos)
195 {
196   if (cur_hpos != hpos || cur_vpos != vpos || cur_hpos < 0)
197     return moveto1(hpos, vpos);
198   else
199     return 1;
200 }
201
202 inline
203 void lj4_printer::hpgl_start()
204 {
205   fputs("\033%1B", stdout);
206 }
207
208 inline
209 void lj4_printer::hpgl_end()
210 {
211   fputs(";\033%0A", stdout);
212 }
213
214 lj4_printer::lj4_printer(int ps)
215 : cur_hpos(-1),
216   cur_font(0),
217   cur_size(0),
218   cur_symbol_set(0),
219   line_thickness(-1),
220   pen_width(-1.0),
221   hpgl_inited(0)
222 {
223   if (7200 % font::res != 0)
224     fatal("invalid resolution %1: resolution must be a factor of 7200",
225           font::res);
226   fputs("\033E", stdout);               // reset
227   if (font::res != 300)
228     printf("\033&u%dD", font::res);     // unit of measure
229   if (ncopies > 0)
230     printf("\033&l%uX", ncopies);
231   paper_size = 0;               // default to letter
232   if (font::papersize) {
233     int n = lookup_paper_size(font::papersize);
234     if (n < 0)
235       error("unknown paper size `%1'", font::papersize);
236     else
237       paper_size = n;
238   }
239   if (ps >= 0)
240     paper_size = ps;
241   printf("\033&l%dA"            // paper size
242          "\033&l%dO"            // orientation
243          "\033&l0E",            // no top margin
244          paper_table[paper_size].code,
245          landscape_flag != 0);
246   if (landscape_flag)
247     x_offset = paper_table[paper_size].x_offset_landscape;
248   else
249     x_offset = paper_table[paper_size].x_offset_portrait;
250   x_offset = (x_offset * font::res) / 300;
251   if (duplex_flag)
252      printf("\033&l%dS", duplex_flag);
253 }
254
255 lj4_printer::~lj4_printer()
256 {
257   fputs("\033E", stdout);
258 }
259
260 void lj4_printer::begin_page(int)
261 {
262 }
263
264 void lj4_printer::end_page(int)
265 {
266   putchar('\f');
267   cur_hpos = -1;
268 }
269
270 void lj4_printer::end_of_line()
271 {
272   cur_hpos = -1;                // force absolute motion
273 }
274
275 inline
276 int is_unprintable(unsigned char c)
277 {
278   return c < 32 && (c == 0 || (7 <= c && c <= 15) || c == 27);
279 }
280
281 void lj4_printer::set_char(int idx, font *f, const environment *env,
282                            int w, const char *)
283 {
284   int code = f->get_code(idx);
285
286   unsigned char ch = code & 0xff;
287   unsigned short symbol_set = code >> 8;
288   if (symbol_set != cur_symbol_set) {
289     printf("\033(%d%c", symbol_set/32, (symbol_set & 31) + 64);
290     cur_symbol_set = symbol_set;
291   }
292   if (f != cur_font) {
293     lj4_font *psf = (lj4_font *)f;
294     // FIXME only output those that are needed
295     printf("\033(s%dp%ds%db%dT",
296            psf->proportional,
297            psf->style,
298            psf->weight,
299            psf->typeface);
300     if (!psf->proportional || !cur_font || !cur_font->proportional)
301       cur_size = 0;
302     cur_font = psf;
303   }
304   if (env->size != cur_size) {
305     if (cur_font->proportional) {
306       static const char *quarters[] = { "", ".25", ".5", ".75" };
307       printf("\033(s%d%sV", env->size/4, quarters[env->size & 3]);
308     }
309     else {
310       double pitch = double(font::res)/w;
311       // PCL uses the next largest pitch, so round it down.
312       pitch = floor(pitch*100.0)/100.0;
313       printf("\033(s%.2fH", pitch);
314     }
315     cur_size = env->size;
316   }
317   if (!moveto(env->hpos, env->vpos))
318     return;
319   if (is_unprintable(ch))
320     fputs("\033&p1X", stdout);
321   putchar(ch);
322   cur_hpos += w;
323 }
324
325 int lj4_printer::moveto1(int hpos, int vpos)
326 {
327   if (hpos < x_offset || vpos < 0)
328     return 0;
329   fputs("\033*p", stdout);
330   if (cur_hpos < 0)
331     printf("%dx%dY", hpos - x_offset, vpos);
332   else {
333     if (cur_hpos != hpos)
334       printf("%s%d%c", hpos > cur_hpos ? "+" : "",
335              hpos - cur_hpos, vpos == cur_vpos ? 'X' : 'x');
336     if (cur_vpos != vpos)
337       printf("%s%dY", vpos > cur_vpos ? "+" : "", vpos - cur_vpos);
338   }
339   cur_hpos = hpos;
340   cur_vpos = vpos;
341   return 1;
342 }
343
344 void lj4_printer::draw(int code, int *p, int np, const environment *env)
345 {
346   switch (code) {
347   case 'R':
348     {
349       if (np != 2) {
350         error("2 arguments required for rule");
351         break;
352       }
353       int hpos = env->hpos;
354       int vpos = env->vpos;
355       int hsize = p[0];
356       int vsize = p[1];
357       if (hsize < 0) {
358         hpos += hsize;
359         hsize = -hsize;
360       }
361       if (vsize < 0) {
362         vpos += vsize;
363         vsize = -vsize;
364       }
365       if (!moveto(hpos, vpos))
366         return;
367       printf("\033*c%da%db0P", hsize, vsize);
368       break;
369     }
370   case 'l':
371     if (np != 2) {
372       error("2 arguments required for line");
373       break;
374     }
375     hpgl_init();
376     if (!moveto(env->hpos, env->vpos))
377       return;
378     hpgl_start();
379     set_line_thickness(env->size, p[0] == 0 && p[1] == 0);
380     printf("PD%d,%d", p[0], p[1]);
381     hpgl_end();
382     break;
383   case 'p':
384   case 'P':
385     {
386       if (np & 1) {
387         error("even number of arguments required for polygon");
388         break;
389       }
390       if (np == 0) {
391         error("no arguments for polygon");
392         break;
393       }
394       hpgl_init();
395       if (!moveto(env->hpos, env->vpos))
396         return;
397       hpgl_start();
398       if (code == 'p')
399         set_line_thickness(env->size);
400       printf("PMPD%d", p[0]);
401       for (int i = 1; i < np; i++)
402         printf(",%d", p[i]);
403       printf("PM2%cP", code == 'p' ? 'E' : 'F');
404       hpgl_end();
405       break;
406     }
407   case '~':
408     {
409       if (np & 1) {
410         error("even number of arguments required for spline");
411         break;
412       }
413       if (np == 0) {
414         error("no arguments for spline");
415         break;
416       }
417       hpgl_init();
418       if (!moveto(env->hpos, env->vpos))
419         return;
420       hpgl_start();
421       set_line_thickness(env->size);
422       printf("PD%d,%d", p[0]/2, p[1]/2);
423       const int tnum = 2;
424       const int tden = 3;
425       if (np > 2) {
426         fputs("BR", stdout);
427         for (int i = 0; i < np - 2; i += 2) {
428           if (i != 0)
429             putchar(',');
430           printf("%d,%d,%d,%d,%d,%d",
431                  (p[i]*tnum)/(2*tden),
432                  (p[i + 1]*tnum)/(2*tden),
433                  p[i]/2 + (p[i + 2]*(tden - tnum))/(2*tden),
434                  p[i + 1]/2 + (p[i + 3]*(tden - tnum))/(2*tden),
435                  (p[i] - p[i]/2) + p[i + 2]/2,
436                  (p[i + 1] - p[i + 1]/2) + p[i + 3]/2);
437         }
438       }
439       printf("PR%d,%d", p[np - 2] - p[np - 2]/2, p[np - 1] - p[np - 1]/2);
440       hpgl_end();
441       break;
442     }
443   case 'c':
444   case 'C':
445     // troff adds an extra argument to C
446     if (np != 1 && !(code == 'C' && np == 2)) {
447       error("1 argument required for circle");
448       break;
449     }
450     hpgl_init();
451     if (!moveto(env->hpos + p[0]/2, env->vpos))
452       return;
453     hpgl_start();
454     if (code == 'c') {
455       set_line_thickness(env->size);
456       printf("CI%d", p[0]/2);
457     }
458     else
459       printf("WG%d,0,360", p[0]/2);
460     hpgl_end();
461     break;
462   case 'e':
463   case 'E':
464     if (np != 2) {
465       error("2 arguments required for ellipse");
466       break;
467     }
468     hpgl_init();
469     if (!moveto(env->hpos + p[0]/2, env->vpos))
470       return;
471     hpgl_start();
472     printf("SC0,%.4f,0,-%.4f,2", hpgl_scale * double(p[0])/p[1], hpgl_scale);
473     if (code == 'e') {
474       set_line_thickness(env->size);
475       printf("CI%d", p[1]/2);
476     }
477     else
478       printf("WG%d,0,360", p[1]/2);
479     printf("SC0,%.4f,0,-%.4f,2", hpgl_scale, hpgl_scale);
480     hpgl_end();
481     break;
482   case 'a':
483     {
484       if (np != 4) {
485         error("4 arguments required for arc");
486         break;
487       }
488       hpgl_init();
489       if (!moveto(env->hpos, env->vpos))
490         return;
491       hpgl_start();
492       set_line_thickness(env->size);
493       double c[2];
494       if (adjust_arc_center(p, c)) {
495         double sweep = ((atan2(p[1] + p[3] - c[1], p[0] + p[2] - c[0])
496                          - atan2(-c[1], -c[0]))
497                         * 180.0/PI);
498         if (sweep > 0.0)
499           sweep -= 360.0;
500         printf("PDAR%d,%d,%f", int(c[0]), int(c[1]), sweep);
501       }
502       else
503         printf("PD%d,%d", p[0] + p[2], p[1] + p[3]);
504       hpgl_end();
505     }
506     break;
507   case 'f':
508     if (np != 1 && np != 2) {
509       error("1 argument required for fill");
510       break;
511     }
512     hpgl_init();
513     hpgl_start();
514     if (p[0] >= 0 && p[0] <= 1000)
515       printf("FT10,%d", p[0]/10);
516     hpgl_end();
517     break;
518   case 'F':
519     // not implemented yet
520     break;
521   case 't':
522     {
523       if (np == 0) {
524         line_thickness = -1;
525       }
526       else {
527         // troff gratuitously adds an extra 0
528         if (np != 1 && np != 2) {
529           error("0 or 1 argument required for thickness");
530           break;
531         }
532         line_thickness = p[0];
533       }
534       break;
535     }
536   default:
537     error("unrecognised drawing command `%1'", char(code));
538     break;
539   }
540 }
541
542 void lj4_printer::hpgl_init()
543 {
544   if (hpgl_inited)
545     return;
546   hpgl_inited = 1;
547   hpgl_scale = double(DEFAULT_HPGL_UNITS)/font::res;
548   printf("\033&f0S"             // push position
549          "\033*p0x0Y"           // move to 0,0
550          "\033*c%dx%dy0T"       // establish picture frame
551          "\033%%1B"             // switch to HPGL
552          "SP1SC0,%.4f,0,-%.4f,2IR0,100,0,100" // set up scaling
553          "LA1,4,2,4"            // round line ends and joins
554          "PR"                   // relative plotting
555          "TR0"                  // opaque
556          ";\033%%1A"            // back to PCL
557          "\033&f1S",            // pop position
558          MAX_PAPER_WIDTH, MAX_PAPER_HEIGHT,
559          hpgl_scale, hpgl_scale);
560 }
561
562 void lj4_printer::set_line_thickness(int size, int dot)
563 {
564   double pw;
565   if (line_thickness < 0)
566     pw = (size * (line_width_factor * 25.4))/(font::sizescale * 72000.0);
567   else
568     pw = line_thickness*25.4/font::res;
569   if (dot && pw < MIN_DOT_PEN_WIDTH)
570     pw = MIN_DOT_PEN_WIDTH;
571   if (pw != pen_width) {
572     printf("PW%f", pw);
573     pen_width = pw;
574   }
575 }
576
577 font *lj4_printer::make_font(const char *nm)
578 {
579   return lj4_font::load_lj4_font(nm);
580 }
581
582 printer *make_printer()
583 {
584   return new lj4_printer(user_paper_size);
585 }
586
587 static
588 int lookup_paper_size(const char *s)
589 {
590   for (unsigned int i = 0;
591        i < sizeof(paper_table)/sizeof(paper_table[0]); i++) {
592     // FIXME Perhaps allow unique prefix.
593     if (strcasecmp(s, paper_table[i].name) == 0)
594       return i;
595   }
596   return -1;
597 }
598
599 static void usage(FILE *stream);
600
601 extern "C" int optopt, optind;
602
603 int main(int argc, char **argv)
604 {
605   setlocale(LC_NUMERIC, "C");
606   program_name = argv[0];
607   static char stderr_buf[BUFSIZ];
608   setbuf(stderr, stderr_buf);
609   int c;
610   static const struct option long_options[] = {
611     { "help", no_argument, 0, CHAR_MAX + 1 },
612     { "version", no_argument, 0, 'v' },
613     { NULL, 0, 0, 0 }
614   };
615   while ((c = getopt_long(argc, argv, "c:d:F:I:lp:vw:", long_options, NULL))
616          != EOF)
617     switch(c) {
618     case 'l':
619       landscape_flag = 1;
620       break;
621     case 'I':
622       // ignore include search path
623       break;
624     case ':':
625       if (optopt == 'd') {
626         fprintf(stderr, "duplex assumed to be long-side\n");
627         duplex_flag = 1;
628       } else
629         fprintf(stderr, "option -%c requires an argument\n", optopt);
630       fflush(stderr);
631       break;
632     case 'd':
633       if (!isdigit(*optarg))    // this ugly hack prevents -d without
634         optind--;               //  args from messing up the arg list
635       duplex_flag = atoi(optarg);
636       if (duplex_flag != 1 && duplex_flag != 2) {
637         fprintf(stderr, "odd value for duplex; assumed to be long-side\n");
638         duplex_flag = 1;
639       }
640       break;
641     case 'p':
642       {
643         int n = lookup_paper_size(optarg);
644         if (n < 0)
645           error("unknown paper size `%1'", optarg);
646         else
647           user_paper_size = n;
648         break;
649       }
650     case 'v':
651       printf("GNU grolj4 (groff) version %s\n", Version_string);
652       exit(0);
653       break;
654     case 'F':
655       font::command_line_font_dir(optarg);
656       break;
657     case 'c':
658       {
659         char *ptr;
660         long n = strtol(optarg, &ptr, 10);
661         if (n == 0 && ptr == optarg)
662           error("argument for -c must be a positive integer");
663         else if (n <= 0 || n > 32767)
664           error("out of range argument for -c");
665         else
666           ncopies = unsigned(n);
667         break;
668       }
669     case 'w':
670       {
671         char *ptr;
672         long n = strtol(optarg, &ptr, 10);
673         if (n == 0 && ptr == optarg)
674           error("argument for -w must be a non-negative integer");
675         else if (n < 0 || n > INT_MAX)
676           error("out of range argument for -w");
677         else
678           line_width_factor = int(n);
679         break;
680       }
681     case CHAR_MAX + 1: // --help
682       usage(stdout);
683       exit(0);
684       break;
685     case '?':
686       usage(stderr);
687       exit(1);
688       break;
689     default:
690       assert(0);
691     }
692   SET_BINARY(fileno(stdout));
693   if (optind >= argc)
694     do_file("-");
695   else {
696     for (int i = optind; i < argc; i++)
697       do_file(argv[i]);
698   }
699   return 0;
700 }
701
702 static void usage(FILE *stream)
703 {
704   fprintf(stream,
705           "usage: %s [-lv] [-d [n]] [-c n] [-p paper_size]\n"
706           "       [-w n] [-F dir] [files ...]\n",
707           program_name);
708 }