Get rid of the old texinfo.
[dragonfly.git] / contrib / groff / src / devices / grotty / tty.cc
1 // -*- C++ -*-
2 /* Copyright (C) 1989-2000, 2001, 2002 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 2, or (at your option) any later
10 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 along
18 with groff; see the file COPYING.  If not, write to the Free Software
19 Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */
20
21 #include "driver.h"
22 #include "device.h"
23
24 extern "C" const char *Version_string;
25
26 #define putstring(s) fputs(s, stdout)
27
28 #ifndef SHRT_MIN
29 #define SHRT_MIN (-32768)
30 #endif
31
32 #ifndef SHRT_MAX
33 #define SHRT_MAX 32767
34 #endif
35
36 #define TAB_WIDTH 8
37
38 static int horizontal_tab_flag = 0;
39 static int form_feed_flag = 0;
40 static int bold_flag = 1;
41 static int underline_flag = 1;
42 static int overstrike_flag = 1;
43 static int draw_flag = 1;
44 static int italic_flag = 0;
45 static int reverse_flag = 0;
46 static int old_drawing_scheme = 0;
47
48 enum {
49   UNDERLINE_MODE = 0x01,
50   BOLD_MODE = 0x02,
51   VDRAW_MODE = 0x04,
52   HDRAW_MODE = 0x08,
53   CU_MODE = 0x10,
54   COLOR_CHANGE = 0x20
55 };
56
57 // Mode to use for bold-underlining.
58 static unsigned char bold_underline_mode = BOLD_MODE|UNDERLINE_MODE;
59
60 #ifndef IS_EBCDIC_HOST
61 #define CSI "\033["
62 #else
63 #define CSI "\047["
64 #endif
65
66 // SGR handling (ISO 6429)
67 #define SGR_BOLD CSI "1m"
68 #define SGR_NO_BOLD CSI "22m"
69 #define SGR_ITALIC CSI "3m"
70 #define SGR_NO_ITALIC CSI "23m"
71 #define SGR_UNDERLINE CSI "4m"
72 #define SGR_NO_UNDERLINE CSI "24m"
73 #define SGR_REVERSE CSI "7m"
74 #define SGR_NO_REVERSE CSI "27m"
75 // many terminals can't handle `CSI 39 m' and `CSI 49 m' to reset
76 // the foreground and bachground color, respectively; thus we use
77 // `CSI 0 m' exclusively
78 #define SGR_DEFAULT CSI "0m"
79
80 #define TTY_MAX_COLORS 8
81 #define DEFAULT_COLOR_IDX TTY_MAX_COLORS
82
83 class tty_font : public font {
84   tty_font(const char *);
85   unsigned char mode;
86 public:
87   ~tty_font();
88   unsigned char get_mode() { return mode; }
89 #if 0
90   void handle_x_command(int argc, const char **argv);
91 #endif
92   static tty_font *load_tty_font(const char *);
93 };
94
95 tty_font *tty_font::load_tty_font(const char *s)
96 {
97   tty_font *f = new tty_font(s);
98   if (!f->load()) {
99     delete f;
100     return 0;
101   }
102   const char *num = f->get_internal_name();
103   long n;
104   if (num != 0 && (n = strtol(num, 0, 0)) != 0)
105     f->mode = int(n & (BOLD_MODE|UNDERLINE_MODE));
106   if (!underline_flag)
107     f->mode &= ~UNDERLINE_MODE;
108   if (!bold_flag)
109     f->mode &= ~BOLD_MODE;
110   if ((f->mode & (BOLD_MODE|UNDERLINE_MODE)) == (BOLD_MODE|UNDERLINE_MODE))
111     f->mode = (f->mode & ~(BOLD_MODE|UNDERLINE_MODE)) | bold_underline_mode;
112   return f;
113 }
114
115 tty_font::tty_font(const char *nm)
116 : font(nm), mode(0)
117 {
118 }
119
120 tty_font::~tty_font()
121 {
122 }
123
124 #if 0
125 void tty_font::handle_x_command(int argc, const char **argv)
126 {
127   if (argc >= 1 && strcmp(argv[0], "bold") == 0)
128     mode |= BOLD_MODE;
129   else if (argc >= 1 && strcmp(argv[0], "underline") == 0)
130     mode |= UNDERLINE_MODE;
131 }
132 #endif
133
134 class glyph {
135   static glyph *free_list;
136 public:
137   glyph *next;
138   short hpos;
139   unsigned int code;
140   unsigned char mode;
141   unsigned char back_color_idx;
142   unsigned char fore_color_idx;
143   void *operator new(size_t);
144   void operator delete(void *);
145   inline int draw_mode() { return mode & (VDRAW_MODE|HDRAW_MODE); }
146   inline int order() {
147     return mode & (VDRAW_MODE|HDRAW_MODE|CU_MODE|COLOR_CHANGE); }
148 };
149
150 glyph *glyph::free_list = 0;
151
152 void *glyph::operator new(size_t)
153 {
154   if (!free_list) {
155     const int BLOCK = 1024;
156     free_list = (glyph *)new char[sizeof(glyph) * BLOCK];
157     for (int i = 0; i < BLOCK - 1; i++)
158       free_list[i].next = free_list + i + 1;
159     free_list[BLOCK - 1].next = 0;
160   }
161   glyph *p = free_list;
162   free_list = free_list->next;
163   p->next = 0;
164   return p;
165 }
166
167 void glyph::operator delete(void *p)
168 {
169   if (p) {
170     ((glyph *)p)->next = free_list;
171     free_list = (glyph *)p;
172   }
173 }
174
175 class tty_printer : public printer {
176   int is_utf8;
177   glyph **lines;
178   int nlines;
179   int cached_v;
180   int cached_vpos;
181   unsigned char curr_fore_idx;
182   unsigned char curr_back_idx;
183   int is_underline;
184   int is_bold;
185   int cu_flag;
186   color tty_colors[TTY_MAX_COLORS];
187   void make_underline();
188   void make_bold(unsigned int);
189   unsigned char color_to_idx(color *col);
190   void add_char(unsigned int, int, int, color *, color *, unsigned char);
191 public:
192   tty_printer(const char *device);
193   ~tty_printer();
194   void set_char(int, font *, const environment *, int, const char *name);
195   void draw(int code, int *p, int np, const environment *env);
196   void special(char *arg, const environment *env, char type);
197   void change_color(const environment *env);
198   void change_fill_color(const environment *env);
199   void put_char(unsigned int);
200   void put_color(unsigned char, int);
201   void begin_page(int) { }
202   void end_page(int page_length);
203   font *make_font(const char *);
204 };
205
206 tty_printer::tty_printer(const char *device) : cached_v(0)
207 {
208   is_utf8 = !strcmp(device, "utf8");
209   tty_colors[0].set_rgb(0,                      // black
210                         0,
211                         0);
212   tty_colors[1].set_rgb(color::MAX_COLOR_VAL,   // red
213                         0,
214                         0);
215   tty_colors[2].set_rgb(0,                      // green
216                         color::MAX_COLOR_VAL,
217                         0);
218   tty_colors[3].set_rgb(color::MAX_COLOR_VAL,   // yellow
219                         color::MAX_COLOR_VAL,
220                         0);
221   tty_colors[4].set_rgb(0,                      // blue
222                         0,
223                         color::MAX_COLOR_VAL);
224   tty_colors[5].set_rgb(color::MAX_COLOR_VAL,   // magenta
225                         0,
226                         color::MAX_COLOR_VAL);
227   tty_colors[6].set_rgb(0,                      // cyan
228                         color::MAX_COLOR_VAL,
229                         color::MAX_COLOR_VAL);
230   tty_colors[7].set_rgb(color::MAX_COLOR_VAL,   // white
231                         color::MAX_COLOR_VAL,
232                         color::MAX_COLOR_VAL);
233   nlines = 66;
234   lines = new glyph *[nlines];
235   for (int i = 0; i < nlines; i++)
236     lines[i] = 0;
237   cu_flag = 0;
238 }
239
240 tty_printer::~tty_printer()
241 {
242   a_delete lines;
243 }
244
245 void tty_printer::make_underline()
246 {
247   if (old_drawing_scheme) {
248     putchar('_');
249     putchar('\b');
250   }
251   else {
252     if (!is_underline) {
253       if (italic_flag)
254         putstring(SGR_ITALIC);
255       else if (reverse_flag)
256         putstring(SGR_REVERSE);
257       else
258         putstring(SGR_UNDERLINE);
259     }
260     is_underline = 1;
261   }
262 }
263
264 void tty_printer::make_bold(unsigned int c)
265 {
266   if (old_drawing_scheme) {
267     put_char(c);
268     putchar('\b');
269   }
270   else {
271     if (!is_bold)
272       putstring(SGR_BOLD);
273     is_bold = 1;
274   }
275 }
276
277 unsigned char tty_printer::color_to_idx(color *col)
278 {
279   if (col->is_default())
280     return DEFAULT_COLOR_IDX;
281   for (int i = 0; i < TTY_MAX_COLORS; i++)
282     if (*col == tty_colors[i])
283       return (unsigned char)i;
284   unsigned r, g, b;
285   col->get_rgb(&r, &g, &b);
286   error("Unknown color (%1, %2, %3) mapped to default", r, g, b);
287   return DEFAULT_COLOR_IDX;
288 }
289
290 void tty_printer::set_char(int i, font *f, const environment *env,
291                            int w, const char *name)
292 {
293   if (w != font::hor)
294     fatal("width of character not equal to horizontal resolution");
295   add_char(f->get_code(i),
296            env->hpos, env->vpos,
297            env->col, env->fill,
298            ((tty_font *)f)->get_mode());
299 }
300
301 void tty_printer::add_char(unsigned int c,
302                            int h, int v,
303                            color *fore, color *back,
304                            unsigned char mode)
305 {
306 #if 0
307   // This is too expensive.
308   if (h % font::hor != 0)
309     fatal("horizontal position not a multiple of horizontal resolution");
310 #endif
311   int hpos = h / font::hor;
312   if (hpos < SHRT_MIN || hpos > SHRT_MAX) {
313     error("character with ridiculous horizontal position discarded");
314     return;
315   }
316   int vpos;
317   if (v == cached_v && cached_v != 0)
318     vpos = cached_vpos;
319   else {
320     if (v % font::vert != 0)
321       fatal("vertical position not a multiple of vertical resolution");
322     vpos = v / font::vert;
323     if (vpos > nlines) {
324       glyph **old_lines = lines;
325       lines = new glyph *[vpos + 1];
326       memcpy(lines, old_lines, nlines * sizeof(glyph *));
327       for (int i = nlines; i <= vpos; i++)
328         lines[i] = 0;
329       a_delete old_lines;
330       nlines = vpos + 1;
331     }
332     // Note that the first output line corresponds to groff
333     // position font::vert.
334     if (vpos <= 0) {
335       error("character above first line discarded");
336       return;
337     }
338     cached_v = v;
339     cached_vpos = vpos;
340   }
341   glyph *g = new glyph;
342   g->hpos = hpos;
343   g->code = c;
344   g->fore_color_idx = color_to_idx(fore);
345   g->back_color_idx = color_to_idx(back);
346   g->mode = mode;
347
348   // The list will be reversed later.  After reversal, it must be in
349   // increasing order of hpos, with COLOR_CHANGE and CU specials before
350   // HDRAW characters before VDRAW characters before normal characters
351   // at each hpos, and otherwise in order of occurrence.
352
353   glyph **pp;
354   for (pp = lines + (vpos - 1); *pp; pp = &(*pp)->next)
355     if ((*pp)->hpos < hpos
356         || ((*pp)->hpos == hpos && (*pp)->order() >= g->order()))
357       break;
358   g->next = *pp;
359   *pp = g;
360 }
361
362 void tty_printer::special(char *arg, const environment *env, char type)
363 {
364   if (type == 'u') {
365     add_char(*arg - '0', env->hpos, env->vpos, env->col, env->fill, CU_MODE);
366     return;
367   }
368   if (type != 'p')
369     return;
370   char *p;
371   for (p = arg; *p == ' ' || *p == '\n'; p++)
372     ;
373   char *tag = p;
374   for (; *p != '\0' && *p != ':' && *p != ' ' && *p != '\n'; p++)
375     ;
376   if (*p == '\0' || strncmp(tag, "tty", p - tag) != 0) {
377     error("X command without `tty:' tag ignored");
378     return;
379   }
380   p++;
381   for (; *p == ' ' || *p == '\n'; p++)
382     ;
383   char *command = p;
384   for (; *p != '\0' && *p != ' ' && *p != '\n'; p++)
385     ;
386   if (*command == '\0') {
387     error("empty X command ignored");
388     return;
389   }
390   if (strncmp(command, "sgr", p - command) == 0) {
391     for (; *p == ' ' || *p == '\n'; p++)
392       ;
393     int n;
394     if (*p != '\0' && sscanf(p, "%d", &n) == 1 && n == 0)
395       old_drawing_scheme = 1;
396     else
397       old_drawing_scheme = 0;
398   }
399 }
400
401 void tty_printer::change_color(const environment *env)
402 {
403   add_char(0, env->hpos, env->vpos, env->col, env->fill, COLOR_CHANGE);
404 }
405
406 void tty_printer::change_fill_color(const environment *env)
407 {
408   add_char(0, env->hpos, env->vpos, env->col, env->fill, COLOR_CHANGE);
409 }
410
411 void tty_printer::draw(int code, int *p, int np, const environment *env)
412 {
413   if (code != 'l' || !draw_flag)
414     return;
415   if (np != 2) {
416     error("2 arguments required for line");
417     return;
418   }
419   if (p[0] == 0) {
420     // vertical line
421     int v = env->vpos;
422     int len = p[1];
423     if (len < 0) {
424       v += len;
425       len = -len;
426     }
427     while (len >= 0) {
428       add_char('|', env->hpos, v, env->col, env->fill, VDRAW_MODE);
429       len -= font::vert;
430       v += font::vert;
431     }
432   }
433   if (p[1] == 0) {
434     // horizontal line
435     int h = env->hpos;
436     int len = p[0];
437     if (len < 0) {
438       h += len;
439       len = -len;
440     }
441     while (len >= 0) {
442       add_char('-', h, env->vpos, env->col, env->fill, HDRAW_MODE);
443       len -= font::hor;
444       h += font::hor;
445     }
446   }
447 }
448
449 void tty_printer::put_char(unsigned int wc)
450 {
451   if (is_utf8 && wc >= 0x80) {
452     char buf[6 + 1];
453     int count;
454     char *p = buf;
455     if (wc < 0x800)
456       count = 1, *p = (unsigned char)((wc >> 6) | 0xc0);
457     else if (wc < 0x10000)
458       count = 2, *p = (unsigned char)((wc >> 12) | 0xe0);
459     else if (wc < 0x200000)
460       count = 3, *p = (unsigned char)((wc >> 18) | 0xf0);
461     else if (wc < 0x4000000)
462       count = 4, *p = (unsigned char)((wc >> 24) | 0xf8);
463     else if (wc <= 0x7fffffff)
464       count = 5, *p = (unsigned char)((wc >> 30) | 0xfC);
465     else
466       return;
467     do *++p = (unsigned char)(((wc >> (6 * --count)) & 0x3f) | 0x80);
468       while (count > 0);
469     *++p = '\0';
470     putstring(buf);
471   }
472   else
473     putchar(wc);
474 }
475
476 void tty_printer::put_color(unsigned char color_index, int back)
477 {
478   if (color_index == DEFAULT_COLOR_IDX) {
479     putstring(SGR_DEFAULT);
480     // set bold and underline again
481     if (is_bold)
482       putstring(SGR_BOLD);
483     if (is_underline) {
484       if (italic_flag)
485         putstring(SGR_ITALIC);
486       else if (reverse_flag)
487         putstring(SGR_REVERSE);
488       else
489         putstring(SGR_UNDERLINE);
490     }
491     // set other color again
492     back = !back;
493     color_index = back ? curr_back_idx : curr_fore_idx;
494   }
495   putstring(CSI);
496   if (back)
497     putchar('4');
498   else
499     putchar('3');
500   putchar(color_index + '0');
501   putchar('m');
502 }
503
504 void tty_printer::end_page(int page_length)
505 {
506   if (page_length % font::vert != 0)
507     error("vertical position at end of page not multiple of vertical resolution");
508   int lines_per_page = page_length / font::vert;
509   int last_line;
510   for (last_line = nlines; last_line > 0; last_line--)
511     if (lines[last_line - 1])
512       break;
513 #if 0
514   if (last_line > lines_per_page) {
515     error("characters past last line discarded");
516     do {
517       --last_line;
518       while (lines[last_line]) {
519         glyph *tem = lines[last_line];
520         lines[last_line] = tem->next;
521         delete tem;
522       }
523     } while (last_line > lines_per_page);
524   }
525 #endif
526   for (int i = 0; i < last_line; i++) {
527     glyph *p = lines[i];
528     lines[i] = 0;
529     glyph *g = 0;
530     while (p) {
531       glyph *tem = p->next;
532       p->next = g;
533       g = p;
534       p = tem;
535     }
536     int hpos = 0;
537     glyph *nextp;
538     curr_fore_idx = DEFAULT_COLOR_IDX;
539     curr_back_idx = DEFAULT_COLOR_IDX;
540     is_underline = 0;
541     is_bold = 0;
542     for (p = g; p; delete p, p = nextp) {
543       nextp = p->next;
544       if (p->mode & CU_MODE) {
545         cu_flag = p->code;
546         continue;
547       }
548       if (nextp && p->hpos == nextp->hpos) {
549         if (p->draw_mode() == HDRAW_MODE &&
550             nextp->draw_mode() == VDRAW_MODE) {
551           nextp->code = '+';
552           continue;
553         }
554         if (p->draw_mode() != 0 && p->draw_mode() == nextp->draw_mode()) {
555           nextp->code = p->code;
556           continue;
557         }
558         if (!overstrike_flag)
559           continue;
560       }
561       if (hpos > p->hpos) {
562         do {
563           putchar('\b');
564           hpos--;
565         } while (hpos > p->hpos);
566       }
567       else {
568         if (horizontal_tab_flag) {
569           for (;;) {
570             int next_tab_pos = ((hpos + TAB_WIDTH) / TAB_WIDTH) * TAB_WIDTH;
571             if (next_tab_pos > p->hpos)
572               break;
573             if (cu_flag)
574               make_underline();
575             else if (!old_drawing_scheme && is_underline) {
576               if (italic_flag)
577                 putstring(SGR_NO_ITALIC);
578               else if (reverse_flag)
579                 putstring(SGR_NO_REVERSE);
580               else
581                 putstring(SGR_NO_UNDERLINE);
582               is_underline = 0;
583             }
584             putchar('\t');
585             hpos = next_tab_pos;
586           }
587         }
588         for (; hpos < p->hpos; hpos++) {
589           if (cu_flag)
590             make_underline();
591           else if (!old_drawing_scheme && is_underline) {
592             if (italic_flag)
593               putstring(SGR_NO_ITALIC);
594             else if (reverse_flag)
595               putstring(SGR_NO_REVERSE);
596             else
597               putstring(SGR_NO_UNDERLINE);
598             is_underline = 0;
599           }
600           putchar(' ');
601         }
602       }
603       assert(hpos == p->hpos);
604       if (p->mode & COLOR_CHANGE) {
605         if (!old_drawing_scheme) {
606           if (p->fore_color_idx != curr_fore_idx) {
607             put_color(p->fore_color_idx, 0);
608             curr_fore_idx = p->fore_color_idx;
609           }
610           if (p->back_color_idx != curr_back_idx) {
611             put_color(p->back_color_idx, 1);
612             curr_back_idx = p->back_color_idx;
613           }
614         }
615         continue;
616       }
617       if (p->mode & UNDERLINE_MODE)
618         make_underline();
619       else if (!old_drawing_scheme && is_underline) {
620         if (italic_flag)
621           putstring(SGR_NO_ITALIC);
622         else if (reverse_flag)
623           putstring(SGR_NO_REVERSE);
624         else
625           putstring(SGR_NO_UNDERLINE);
626         is_underline = 0;
627       }
628       if (p->mode & BOLD_MODE)
629         make_bold(p->code);
630       else if (!old_drawing_scheme && is_bold) {
631         putstring(SGR_NO_BOLD);
632         is_bold = 0;
633       }
634       if (!old_drawing_scheme) {
635         if (p->fore_color_idx != curr_fore_idx) {
636           put_color(p->fore_color_idx, 0);
637           curr_fore_idx = p->fore_color_idx;
638         }
639         if (p->back_color_idx != curr_back_idx) {
640           put_color(p->back_color_idx, 1);
641           curr_back_idx = p->back_color_idx;
642         }
643       }
644       put_char(p->code);
645       hpos++;
646     }
647     if (!old_drawing_scheme
648         && (is_bold || is_underline
649             || curr_fore_idx != DEFAULT_COLOR_IDX
650             || curr_back_idx != DEFAULT_COLOR_IDX))
651       putstring(SGR_DEFAULT);
652     putchar('\n');
653   }
654   if (form_feed_flag) {
655     if (last_line < lines_per_page)
656       putchar('\f');
657   }
658   else {
659     for (; last_line < lines_per_page; last_line++)
660       putchar('\n');
661   }
662 }
663
664 font *tty_printer::make_font(const char *nm)
665 {
666   return tty_font::load_tty_font(nm);
667 }
668
669 printer *make_printer()
670 {
671   return new tty_printer(device);
672 }
673
674 static void usage(FILE *stream);
675
676 int main(int argc, char **argv)
677 {
678   program_name = argv[0];
679   static char stderr_buf[BUFSIZ];
680   if (getenv("GROFF_NO_SGR"))
681     old_drawing_scheme = 1;
682   setbuf(stderr, stderr_buf);
683   int c;
684   static const struct option long_options[] = {
685     { "help", no_argument, 0, CHAR_MAX + 1 },
686     { "version", no_argument, 0, 'v' },
687     { NULL, 0, 0, 0 }
688   };
689   while ((c = getopt_long(argc, argv, "bBcdfF:hioruUv", long_options, NULL))
690          != EOF)
691     switch(c) {
692     case 'v':
693       printf("GNU grotty (groff) version %s\n", Version_string);
694       exit(0);
695       break;
696     case 'i':
697       // Use italic font instead of underlining.
698       italic_flag = 1;
699       break;
700     case 'b':
701       // Do not embolden by overstriking.
702       bold_flag = 0;
703       break;
704     case 'c':
705       // Use old scheme for emboldening and underline.
706       old_drawing_scheme = 1;
707       break;
708     case 'u':
709       // Do not underline.
710       underline_flag = 0;
711       break;
712     case 'o':
713       // Do not overstrike (other than emboldening and underlining).
714       overstrike_flag = 0;
715       break;
716     case 'r':
717       // Use reverse mode instead of underlining.
718       reverse_flag = 1;
719       break;
720     case 'B':
721       // Do bold-underlining as bold.
722       bold_underline_mode = BOLD_MODE;
723       break;
724     case 'U':
725       // Do bold-underlining as underlining.
726       bold_underline_mode = UNDERLINE_MODE;
727       break;
728     case 'h':
729       // Use horizontal tabs.
730       horizontal_tab_flag = 1;
731       break;
732     case 'f':
733       form_feed_flag = 1;
734       break;
735     case 'F':
736       font::command_line_font_dir(optarg);
737       break;
738     case 'd':
739       // Ignore \D commands.
740       draw_flag = 0;
741       break;
742     case CHAR_MAX + 1: // --help
743       usage(stdout);
744       exit(0);
745       break;
746     case '?':
747       usage(stderr);
748       exit(1);
749       break;
750     default:
751       assert(0);
752     }
753   if (old_drawing_scheme) {
754     italic_flag = 0;
755     reverse_flag = 0;
756   }
757   else {
758     bold_underline_mode = BOLD_MODE|UNDERLINE_MODE;
759     bold_flag = 1;
760     underline_flag = 1;
761   }
762   if (optind >= argc)
763     do_file("-");
764   else {
765     for (int i = optind; i < argc; i++)
766       do_file(argv[i]);
767   }
768   delete pr;
769   return 0;
770 }
771
772 static void usage(FILE *stream)
773 {
774   fprintf(stream, "usage: %s [-bBcdfhioruUv] [-F dir] [files ...]\n",
775           program_name);
776 }