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