Line data Source code
1 : /* Copyright 1989-2025 Free Software Foundation, Inc.
2 : Written by James Clark (jjc@jclark.com)
3 : OSC 8 support by G. Branden Robinson
4 :
5 : This file is part of groff, the GNU roff typesetting system.
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 : #ifdef HAVE_CONFIG_H
21 : #include <config.h>
22 : #endif
23 :
24 : #include <limits.h> // CHAR_MAX
25 : #include <locale.h> // setlocale()
26 : #include <stdio.h> // EOF, FILE, fprintf(), fputs(), printf(),
27 : // putchar(), setbuf(), stderr, stdout
28 : #include <stdlib.h> // exit(), EXIT_SUCCESS, getenv(), strtol()
29 :
30 : #include <getopt.h> // getopt_long()
31 :
32 : #include "device.h"
33 : #include "driver.h"
34 : #include "ptable.h"
35 :
36 : typedef signed char schar;
37 :
38 : declare_ptable(schar)
39 351168 : implement_ptable(schar)
40 :
41 : extern "C" const char *Version_string;
42 :
43 : #define putstring(s) fputs(s, stdout)
44 :
45 : #ifndef SHRT_MIN
46 : #define SHRT_MIN (-32768)
47 : #endif
48 :
49 : #ifndef SHRT_MAX
50 : #define SHRT_MAX 32767
51 : #endif
52 :
53 : #define TAB_WIDTH 8
54 :
55 : // A character of the output device fits in a 32-bit word.
56 : typedef unsigned int output_character;
57 :
58 : static bool want_horizontal_tabs = false;
59 : static bool want_form_feeds = false;
60 : static bool want_emboldening_by_overstriking = true;
61 : static bool do_bold;
62 : static bool want_italics_by_underlining = true;
63 : static bool do_underline;
64 : static bool want_glyph_composition_by_overstriking = true;
65 : static bool allow_drawing_commands = true;
66 : static bool want_sgr_italics = false;
67 : static bool do_sgr_italics;
68 : static bool want_reverse_video_for_italics = false;
69 : static bool do_reverse_video;
70 : static bool use_overstriking_drawing_scheme = false;
71 : static bool want_sgr_truecolor = false;
72 : static void update_options();
73 : static void usage(FILE *stream);
74 :
75 : static int hline_char = '-';
76 : static int vline_char = '|';
77 :
78 : enum {
79 : UNDERLINE_MODE = 0x01,
80 : BOLD_MODE = 0x02,
81 : VDRAW_MODE = 0x04,
82 : HDRAW_MODE = 0x08,
83 : CU_MODE = 0x10,
84 : COLOR_CHANGE = 0x20,
85 : START_LINE = 0x40,
86 : END_LINE = 0x80
87 : };
88 :
89 : // Mode to use for bold-underlining.
90 : static unsigned char bold_underline_mode_option = BOLD_MODE|UNDERLINE_MODE;
91 : static unsigned char bold_underline_mode;
92 :
93 : #define CSI "\033["
94 : #define OSC8 "\033]8"
95 : #define ST "\033\\"
96 :
97 : // SGR handling (ISO 6429)
98 : #define SGR_BOLD CSI "1m"
99 : #define SGR_NO_BOLD CSI "22m"
100 : #define SGR_ITALIC CSI "3m"
101 : #define SGR_NO_ITALIC CSI "23m"
102 : #define SGR_UNDERLINE CSI "4m"
103 : #define SGR_NO_UNDERLINE CSI "24m"
104 : #define SGR_REVERSE CSI "7m"
105 : #define SGR_NO_REVERSE CSI "27m"
106 : // many terminals can't handle 'CSI 39 m' and 'CSI 49 m' to reset
107 : // the foreground and background color, respectively; we thus use
108 : // 'CSI 0 m' exclusively
109 : #define SGR_DEFAULT CSI "0m"
110 :
111 : const int DEFAULT_COLOR_IDX = -1;
112 :
113 : class tty_font : public font {
114 : tty_font(const char *);
115 : unsigned char mode;
116 : public:
117 : ~tty_font();
118 1040805 : unsigned char get_mode() { return mode; }
119 : #if 0
120 : void handle_x_command(int argc, const char **argv);
121 : #endif
122 : static tty_font *load_tty_font(const char *);
123 : };
124 :
125 1300 : tty_font *tty_font::load_tty_font(const char *s)
126 : {
127 1300 : tty_font *f = new tty_font(s);
128 1300 : if (!f->load()) {
129 0 : delete f;
130 0 : return 0;
131 : }
132 1300 : const char *num = f->get_internal_name();
133 : long n;
134 1300 : if (num != 0 && (n = strtol(num, 0, 0)) != 0)
135 363 : f->mode = (unsigned char)(n & (BOLD_MODE|UNDERLINE_MODE));
136 1300 : if (!do_underline)
137 725 : f->mode &= ~UNDERLINE_MODE;
138 1300 : if (!do_bold)
139 725 : f->mode &= ~BOLD_MODE;
140 1300 : if ((f->mode & (BOLD_MODE|UNDERLINE_MODE)) == (BOLD_MODE|UNDERLINE_MODE))
141 3 : f->mode = (unsigned char)((f->mode & ~(BOLD_MODE|UNDERLINE_MODE))
142 3 : | bold_underline_mode);
143 1300 : return f;
144 : }
145 :
146 1300 : tty_font::tty_font(const char *nm)
147 1300 : : font(nm), mode(0)
148 : {
149 1300 : }
150 :
151 2600 : tty_font::~tty_font()
152 : {
153 2600 : }
154 :
155 : #if 0
156 : void tty_font::handle_x_command(int argc, const char **argv)
157 : {
158 : if (argc >= 1 && strcmp(argv[0], "bold") == 0)
159 : mode |= BOLD_MODE;
160 : else if (argc >= 1 && strcmp(argv[0], "underline") == 0)
161 : mode |= UNDERLINE_MODE;
162 : }
163 : #endif
164 :
165 : class tty_glyph {
166 : public:
167 : tty_glyph *next;
168 : int w;
169 : int hpos;
170 : unsigned int code;
171 : unsigned char mode;
172 : long back_color_idx;
173 : long fore_color_idx;
174 108492 : inline int draw_mode() { return mode & (VDRAW_MODE|HDRAW_MODE); }
175 71956 : inline int order() {
176 71956 : return mode & (VDRAW_MODE|HDRAW_MODE|CU_MODE|COLOR_CHANGE); }
177 : };
178 :
179 :
180 : class tty_printer : public printer {
181 : tty_glyph **lines;
182 : int nlines;
183 : int cached_v;
184 : int cached_vpos;
185 : long curr_fore_idx;
186 : long curr_back_idx;
187 : bool is_underlining;
188 : bool is_boldfacing;
189 : bool is_continuously_underlining;
190 : PTABLE(schar) tty_colors;
191 : void make_underline(int);
192 : void make_bold(output_character, int);
193 : long color_to_idx(color *);
194 : void add_char(output_character, int, int, int, color *, color *,
195 : unsigned char);
196 : void simple_add_char(const output_character, const environment *);
197 : char *make_rgb_string(unsigned int, unsigned int, unsigned int);
198 : bool has_color(unsigned int, unsigned int, unsigned int, long *,
199 : schar = DEFAULT_COLOR_IDX);
200 : void line(int, int, int, int, color *, color *);
201 : void draw_line(int *, int, const environment *);
202 : void draw_polygon(int *, int, const environment *);
203 : void special_link(const char *, const environment *);
204 : public:
205 : tty_printer();
206 : ~tty_printer();
207 : void set_char(glyph *, font *, const environment *, int, const char *);
208 : void draw(int, int *, int, const environment *);
209 : void special(char *, const environment *, char);
210 : void change_color(const environment * const);
211 : void change_fill_color(const environment * const);
212 : void put_char(output_character);
213 : void put_color(long, int);
214 1058 : void begin_page(int) { }
215 : void end_page(int);
216 : font *make_font(const char *);
217 : };
218 :
219 7552 : char *tty_printer::make_rgb_string(unsigned int r,
220 : unsigned int g,
221 : unsigned int b)
222 : {
223 7552 : char *s = new char[8];
224 7552 : s[0] = char(r >> 8);
225 7552 : s[1] = char(r & 0xff);
226 7552 : s[2] = char(g >> 8);
227 7552 : s[3] = char(g & 0xff);
228 7552 : s[4] = char(b >> 8);
229 7552 : s[5] = char(b & 0xff);
230 7552 : s[6] = char(0x80);
231 7552 : s[7] = 0;
232 : // avoid null-bytes in string
233 52864 : for (int i = 0; i < 6; i++)
234 45312 : if (!s[i]) {
235 22656 : s[i] = 1;
236 22656 : s[6] |= 1 << i;
237 : }
238 7552 : return s;
239 : }
240 :
241 7552 : bool tty_printer::has_color(unsigned int r,
242 : unsigned int g,
243 : unsigned int b, long *idx, schar value)
244 : {
245 7552 : bool is_known_color = true;
246 7552 : if (!want_sgr_truecolor) {
247 7552 : char *s = make_rgb_string(r, g, b);
248 7552 : schar *i = tty_colors.lookup(s);
249 7552 : if (0 /* nullptr */ == i) {
250 7552 : is_known_color = false;
251 7552 : i = new schar[1];
252 7552 : *i = value;
253 7552 : tty_colors.define(s, i);
254 : }
255 7552 : *idx = *i;
256 7552 : delete[] s;
257 : }
258 : else {
259 0 : *idx=((r>>8)<<16) + ((g>>8)<<8) + (b>>8);
260 : }
261 7552 : return is_known_color;
262 : }
263 :
264 944 : tty_printer::tty_printer() : cached_v(0)
265 : {
266 944 : if (font::is_unicode) {
267 31 : hline_char = 0x2500;
268 31 : vline_char = 0x2502;
269 : }
270 : // TODO: Skip color setup if terminfo `colors` capability is "-1".
271 : long dummy;
272 : // Create the eight ANSI X3.64/ECMA-48/ISO 6429 standard colors.
273 : // black, white
274 944 : (void) has_color(0, 0, 0, &dummy, 0);
275 944 : (void) has_color(color::MAX_COLOR_VAL,
276 : color::MAX_COLOR_VAL,
277 : color::MAX_COLOR_VAL, &dummy, 7);
278 : // red, green, blue
279 944 : (void) has_color(color::MAX_COLOR_VAL, 0, 0, &dummy, 1);
280 944 : (void) has_color(0, color::MAX_COLOR_VAL, 0, &dummy, 2);
281 944 : (void) has_color(0, 0, color::MAX_COLOR_VAL, &dummy, 4);
282 : // yellow, magenta, cyan
283 944 : (void) has_color(color::MAX_COLOR_VAL, color::MAX_COLOR_VAL, 0,
284 : &dummy, 3);
285 944 : (void) has_color(color::MAX_COLOR_VAL, 0, color::MAX_COLOR_VAL,
286 : &dummy, 5);
287 944 : (void) has_color(0, color::MAX_COLOR_VAL, color::MAX_COLOR_VAL,
288 : &dummy, 6);
289 944 : nlines = 66;
290 944 : lines = new tty_glyph *[nlines];
291 63248 : for (int i = 0; i < nlines; i++)
292 62304 : lines[i] = 0;
293 944 : is_continuously_underlining = false;
294 944 : }
295 :
296 1888 : tty_printer::~tty_printer()
297 : {
298 944 : current_lineno = 0; // At this point, we've read all the input.
299 944 : delete[] lines;
300 1888 : }
301 :
302 74987 : void tty_printer::make_underline(int w)
303 : {
304 74987 : if (use_overstriking_drawing_scheme) {
305 0 : if (!w)
306 0 : warning("can't underline zero-width character");
307 : else {
308 0 : putchar('_');
309 0 : putchar('\b');
310 : }
311 : }
312 : else {
313 74987 : if (!is_underlining) {
314 12013 : if (do_sgr_italics)
315 0 : putstring(SGR_ITALIC);
316 12013 : else if (do_reverse_video)
317 0 : putstring(SGR_REVERSE);
318 : else
319 12013 : putstring(SGR_UNDERLINE);
320 : }
321 74987 : is_underlining = true;
322 : }
323 74987 : }
324 :
325 57715 : void tty_printer::make_bold(output_character c, int w)
326 : {
327 57715 : if (use_overstriking_drawing_scheme) {
328 0 : if (!w)
329 0 : warning("can't print zero-width character in bold");
330 : else {
331 0 : put_char(c);
332 0 : putchar('\b');
333 : }
334 : }
335 : else {
336 57715 : if (!is_boldfacing)
337 12279 : putstring(SGR_BOLD);
338 57715 : is_boldfacing = true;
339 : }
340 57715 : }
341 :
342 2185952 : long tty_printer::color_to_idx(color *col)
343 : {
344 2185952 : if (col->is_default())
345 2185952 : return DEFAULT_COLOR_IDX;
346 : unsigned int r, g, b;
347 0 : col->get_rgb(&r, &g, &b);
348 : long idx;
349 0 : if (!has_color(r, g, b, &idx)) {
350 0 : char *s = col->print_color();
351 0 : error("unsupported color '%1' mapped to default", s);
352 0 : delete[] s;
353 : }
354 0 : return idx;
355 : }
356 :
357 1040805 : void tty_printer::set_char(glyph *g, font *f, const environment *env,
358 : int w, const char *)
359 : {
360 1040805 : if (w % font::hor != 0)
361 0 : fatal("glyph width is not a multiple of horizontal motion quantum");
362 1040805 : add_char(f->get_code(g), w,
363 1040805 : env->hpos, env->vpos,
364 1040805 : env->col, env->fill,
365 1040805 : ((tty_font *)f)->get_mode());
366 1040805 : }
367 :
368 1092976 : void tty_printer::add_char(output_character c, int w,
369 : int h, int v,
370 : color *fore, color *back,
371 : unsigned char mode)
372 : {
373 : #if 0
374 : // This is too expensive.
375 : if (h % font::hor != 0)
376 : fatal("horizontal position not a multiple of horizontal motion quantum");
377 : #endif
378 1092976 : int hpos = h / font::hor;
379 1092976 : if (hpos < SHRT_MIN || hpos > SHRT_MAX) {
380 0 : error("character with ridiculous horizontal position discarded");
381 0 : return;
382 : }
383 : int vpos;
384 1092976 : if (v == cached_v && cached_v != 0)
385 1065345 : vpos = cached_vpos;
386 : else {
387 27631 : if (v % font::vert != 0)
388 0 : fatal("vertical position not a multiple of vertical motion"
389 : " quantum");
390 27631 : vpos = v / font::vert;
391 27631 : if (vpos > nlines) {
392 10196 : tty_glyph **old_lines = lines;
393 10196 : lines = new tty_glyph *[vpos + 1];
394 10196 : memcpy(lines, old_lines, nlines * sizeof(tty_glyph *));
395 32493 : for (int i = nlines; i <= vpos; i++)
396 22297 : lines[i] = 0;
397 10196 : delete[] old_lines;
398 10196 : nlines = vpos + 1;
399 : }
400 : // Note that the first output line corresponds to groff
401 : // position font::vert.
402 27631 : if (vpos <= 0) {
403 0 : error("output above first line discarded");
404 0 : return;
405 : }
406 27631 : cached_v = v;
407 27631 : cached_vpos = vpos;
408 : }
409 1092976 : tty_glyph *g = new tty_glyph;
410 1092976 : g->w = w;
411 1092976 : g->hpos = hpos;
412 1092976 : g->code = c;
413 1092976 : g->fore_color_idx = color_to_idx(fore);
414 1092976 : g->back_color_idx = color_to_idx(back);
415 1092976 : g->mode = mode;
416 :
417 : // The list will be reversed later. After reversal, it must be in
418 : // increasing order of hpos, with COLOR_CHANGE and CU specials before
419 : // HDRAW characters before VDRAW characters before normal characters
420 : // at each hpos, and otherwise in order of occurrence.
421 :
422 : tty_glyph **pp;
423 1106073 : for (pp = lines + (vpos - 1); *pp; pp = &(*pp)->next)
424 2158102 : if ((*pp)->hpos < hpos
425 1079051 : || ((*pp)->hpos == hpos && (*pp)->order() >= g->order()))
426 1065954 : break;
427 1092976 : g->next = *pp;
428 1092976 : *pp = g;
429 : }
430 :
431 34044 : void tty_printer::simple_add_char(const output_character c,
432 : const environment *env)
433 : {
434 34044 : add_char(c, 0, env->hpos, env->vpos, env->col, env->fill, 0);
435 34044 : }
436 :
437 2375 : void tty_printer::special(char *arg, const environment *env, char type)
438 : {
439 2375 : if (type == 'u') {
440 0 : add_char(*arg - '0', 0, env->hpos, env->vpos, env->col, env->fill,
441 : CU_MODE);
442 0 : return;
443 : }
444 2375 : if (type != 'p')
445 0 : return;
446 : char *p;
447 2375 : for (p = arg; *p == ' ' || *p == '\n'; p++)
448 : ;
449 2375 : char *tag = p;
450 9500 : for (; *p != '\0' && *p != ':' && *p != ' ' && *p != '\n'; p++)
451 : ;
452 2375 : if (*p == '\0' || strncmp(tag, "tty", p - tag) != 0) {
453 0 : *p = '\0'; // terminate string at colon
454 0 : error("X command with '%1' tag ignored; expected 'tty'", tag);
455 0 : return;
456 : }
457 2375 : p++;
458 4747 : for (; *p == ' ' || *p == '\n'; p++)
459 : ;
460 2375 : char *command = p;
461 11863 : for (; *p != '\0' && *p != ' ' && *p != '\n'; p++)
462 : ;
463 2375 : if (*command == '\0') {
464 3 : error("empty X command ignored");
465 3 : return;
466 : }
467 2372 : if (strncmp(command, "link", p - command) == 0)
468 2372 : special_link(p, env);
469 : else
470 0 : warning("unrecognized X command '%1' ignored", command);
471 : }
472 :
473 : // Produce an OSC 8 hyperlink. Given ditroff input of the form:
474 : // x X tty: link [URI[ KEY=VALUE] ...]
475 : // produce "OSC 8 [;KEY=VALUE];[URI] ST". KEY/VALUE pairs can be
476 : // repeated arbitrarily and are separated by colons. Omission of the
477 : // URI ends the hyperlink that was begun by specifying it. See
478 : // <https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda>.
479 2372 : void tty_printer::special_link(const char *arg, const environment *env)
480 : {
481 : static bool is_link_active = false;
482 2372 : if (use_overstriking_drawing_scheme)
483 64 : return;
484 9232 : for (const char *s = OSC8; *s != '\0'; s++)
485 6924 : simple_add_char(*s, env);
486 2308 : simple_add_char(';', env);
487 2308 : char c = *arg;
488 2308 : if ('\0' == c || '\n' == c) {
489 1154 : simple_add_char(';', env);
490 1154 : if (!is_link_active)
491 1 : warning("ending hyperlink when none was started");
492 1154 : is_link_active = false;
493 : }
494 : else {
495 : // Our caller ensures that we see whitespace after 'link'.
496 1154 : assert((' ' == c) || ('\t' == c));
497 1154 : if (is_link_active) {
498 1 : warning("new hyperlink started without ending previous one;"
499 : " recovering");
500 1 : simple_add_char(';', env);
501 6 : for (const char *s = ST OSC8; *s != '\0'; s++)
502 5 : simple_add_char(*s, env);
503 1 : simple_add_char(';', env);
504 : }
505 1154 : is_link_active = true;
506 1154 : do
507 2308 : c = *arg++;
508 2308 : while ((' ' == c) || ('\t' == c));
509 1154 : arg--;
510 : // The first argument is the URI.
511 1154 : const char *uri = arg;
512 17881 : do
513 19035 : c = *arg++;
514 19035 : while ((c != '\0') && (c != ' ') && (c != '\t'));
515 1154 : arg--;
516 1154 : ptrdiff_t uri_len = arg - uri;
517 : // Any remaining arguments are "key=value" pairs.
518 1154 : const char *pair = 0;
519 1154 : bool done = false;
520 0 : do {
521 1154 : if (pair != 0)
522 0 : simple_add_char(':', env);
523 1154 : pair = arg;
524 1154 : bool in_pair = true;
525 0 : do {
526 1154 : c = *arg++;
527 1154 : if ('\0' == c)
528 1154 : done = true;
529 0 : else if ((' ' == c) || ('\t' == c))
530 0 : in_pair = false;
531 : else
532 0 : simple_add_char(c, env);
533 1154 : } while (!done && in_pair);
534 1154 : } while (!done);
535 1154 : simple_add_char(';', env);
536 19035 : for (size_t i = uri_len; i > 0; i--)
537 17881 : simple_add_char(*uri++, env);
538 : }
539 6924 : for (const char *s = ST; *s != '\0'; s++)
540 4616 : simple_add_char(*s, env);
541 : }
542 :
543 940 : void tty_printer::change_color(const environment * const env)
544 : {
545 940 : add_char(0, 0, env->hpos, env->vpos, env->col, env->fill, COLOR_CHANGE);
546 940 : }
547 :
548 941 : void tty_printer::change_fill_color(const environment * const env)
549 : {
550 941 : add_char(0, 0, env->hpos, env->vpos, env->col, env->fill, COLOR_CHANGE);
551 941 : }
552 :
553 430 : void tty_printer::draw(int code, int *p, int np, const environment *env)
554 : {
555 430 : if (!allow_drawing_commands)
556 0 : return;
557 430 : if (code == 'l')
558 430 : draw_line(p, np, env);
559 0 : else if (code == 'p')
560 0 : draw_polygon(p, np, env);
561 : else
562 0 : warning("ignoring unsupported drawing command '%1'", char(code));
563 : }
564 :
565 0 : void tty_printer::draw_polygon(int *p, int np, const environment *env)
566 : {
567 0 : if (np & 1) {
568 0 : error("even number of arguments required for polygon");
569 0 : return;
570 : }
571 0 : if (np == 0) {
572 0 : error("no arguments for polygon");
573 0 : return;
574 : }
575 : // We draw only polygons that consist entirely of horizontal and
576 : // vertical lines.
577 0 : int hpos = 0;
578 0 : int vpos = 0;
579 0 : for (int i = 0; i < np; i += 2) {
580 0 : if (!(p[i] == 0 || p[i + 1] == 0))
581 0 : return;
582 0 : hpos += p[i];
583 0 : vpos += p[i + 1];
584 : }
585 0 : if (!(hpos == 0 || vpos == 0))
586 0 : return;
587 0 : int start_hpos = env->hpos;
588 0 : int start_vpos = env->vpos;
589 0 : hpos = start_hpos;
590 0 : vpos = start_vpos;
591 0 : for (int i = 0; i < np; i += 2) {
592 0 : line(hpos, vpos, p[i], p[i + 1], env->col, env->fill);
593 0 : hpos += p[i];
594 0 : vpos += p[i + 1];
595 : }
596 0 : line(hpos, vpos, start_hpos - hpos, start_vpos - vpos,
597 0 : env->col, env->fill);
598 : }
599 :
600 430 : void tty_printer::draw_line(int *p, int np, const environment *env)
601 : {
602 430 : if (np != 2) {
603 0 : error("2 arguments required for line");
604 0 : return;
605 : }
606 430 : line(env->hpos, env->vpos, p[0], p[1], env->col, env->fill);
607 : }
608 :
609 430 : void tty_printer::line(int hpos, int vpos, int dx, int dy,
610 : color *col, color *fill)
611 : {
612 : // XXX: zero-length lines get drawn as '+' crossings in nroff, even
613 : // when there is no crossing, but they nevertheless occur frequently
614 : // in input. Does tbl produce them?
615 : #if 0
616 : if (0 == dx)
617 : fatal("cannot draw zero-length horizontal line");
618 : if (0 == dy)
619 : fatal("cannot draw zero-length vertical line");
620 : #endif
621 430 : if ((dx != 0) && (dy != 0))
622 0 : warning("cannot draw diagonal line");
623 430 : if (dx % font::hor != 0)
624 0 : fatal("length of horizontal line %1 is not a multiple of horizontal"
625 0 : " motion quantum %2", dx, font::hor);
626 430 : if (dy % font::vert != 0)
627 0 : fatal("length of vertical line %1 is not a multiple of vertical"
628 0 : " motion quantum %2", dy, font::vert);
629 430 : if (dx == 0) {
630 : // vertical line
631 166 : int v = vpos;
632 166 : int len = dy;
633 166 : if (len < 0) {
634 120 : v += len;
635 120 : len = -len;
636 : }
637 166 : if (len == 0)
638 4 : add_char(vline_char, font::hor, hpos, v, col, fill,
639 : VDRAW_MODE|START_LINE|END_LINE);
640 : else {
641 162 : add_char(vline_char, font::hor, hpos, v, col, fill,
642 : VDRAW_MODE|START_LINE);
643 162 : len -= font::vert;
644 162 : v += font::vert;
645 628 : while (len > 0) {
646 466 : add_char(vline_char, font::hor, hpos, v, col, fill,
647 : VDRAW_MODE|START_LINE|END_LINE);
648 466 : len -= font::vert;
649 466 : v += font::vert;
650 : }
651 162 : add_char(vline_char, font::hor, hpos, v, col, fill,
652 : VDRAW_MODE|END_LINE);
653 : }
654 : }
655 430 : if (dy == 0) {
656 : // horizontal line
657 268 : int h = hpos;
658 268 : int len = dx;
659 268 : if (len < 0) {
660 0 : h += len;
661 0 : len = -len;
662 : }
663 268 : if (len == 0)
664 4 : add_char(hline_char, font::hor, h, vpos, col, fill,
665 : HDRAW_MODE|START_LINE|END_LINE);
666 : else {
667 264 : add_char(hline_char, font::hor, h, vpos, col, fill,
668 : HDRAW_MODE|START_LINE);
669 264 : len -= font::hor;
670 264 : h += font::hor;
671 15184 : while (len > 0) {
672 14920 : add_char(hline_char, font::hor, h, vpos, col, fill,
673 : HDRAW_MODE|START_LINE|END_LINE);
674 14920 : len -= font::hor;
675 14920 : h += font::hor;
676 : }
677 264 : add_char(hline_char, font::hor, h, vpos, col, fill,
678 : HDRAW_MODE|END_LINE);
679 : }
680 : }
681 430 : }
682 :
683 1090518 : void tty_printer::put_char(output_character wc)
684 : {
685 1090518 : if (font::is_unicode && wc >= 0x80) {
686 : char buf[6 + 1];
687 : int count;
688 24525 : char *p = buf;
689 24525 : if (wc < 0x800)
690 490 : count = 1, *p = (unsigned char)((wc >> 6) | 0xc0);
691 24035 : else if (wc < 0x10000)
692 24035 : count = 2, *p = (unsigned char)((wc >> 12) | 0xe0);
693 0 : else if (wc < 0x200000)
694 0 : count = 3, *p = (unsigned char)((wc >> 18) | 0xf0);
695 0 : else if (wc < 0x4000000)
696 0 : count = 4, *p = (unsigned char)((wc >> 24) | 0xf8);
697 0 : else if (wc <= 0x7fffffff)
698 0 : count = 5, *p = (unsigned char)((wc >> 30) | 0xfC);
699 : else
700 0 : return;
701 24035 : do *++p = (unsigned char)(((wc >> (6 * --count)) & 0x3f) | 0x80);
702 48560 : while (count > 0);
703 24525 : *++p = '\0';
704 24525 : putstring(buf);
705 : }
706 : else
707 1065993 : putchar(wc);
708 : }
709 :
710 0 : void tty_printer::put_color(long color_index, int back)
711 : {
712 0 : if (!want_sgr_truecolor) {
713 0 : if (DEFAULT_COLOR_IDX == color_index) {
714 0 : putstring(SGR_DEFAULT);
715 : // set bold and underline again
716 0 : if (is_boldfacing)
717 0 : putstring(SGR_BOLD);
718 0 : if (is_underlining) {
719 0 : if (do_sgr_italics)
720 0 : putstring(SGR_ITALIC);
721 0 : else if (do_reverse_video)
722 0 : putstring(SGR_REVERSE);
723 : else
724 0 : putstring(SGR_UNDERLINE);
725 : }
726 : // set other color again
727 0 : back = !back;
728 0 : color_index = back ? curr_back_idx : curr_fore_idx;
729 : }
730 0 : if (color_index != DEFAULT_COLOR_IDX) {
731 0 : putstring(CSI);
732 0 : if (back)
733 0 : putchar('4');
734 : else
735 0 : putchar('3');
736 0 : putchar(color_index + '0');
737 0 : putchar('m');
738 : }
739 : }
740 : else {
741 0 : if (DEFAULT_COLOR_IDX == color_index) {
742 0 : putstring(SGR_DEFAULT);
743 0 : back = !back;
744 0 : color_index = back ? curr_back_idx : curr_fore_idx;
745 0 : if (DEFAULT_COLOR_IDX == color_index)
746 0 : return;
747 : }
748 0 : putstring(CSI);
749 0 : int fb = back ? 48 : 38;
750 0 : const size_t buflen = sizeof "48;2;255;255;255m";
751 : char buf[buflen];
752 0 : size_t written = snprintf(buf, buflen, "%d;2;%lu;%lu;%lum", fb,
753 : (color_index >> 16),
754 0 : ((color_index >> 8) & 0xff),
755 0 : (color_index & 0xff));
756 0 : assert(written < buflen);
757 0 : putstring(buf);
758 : }
759 : }
760 :
761 : // The possible Unicode combinations for crossing characters.
762 : //
763 : // ' ' = 0, ' -' = 4, '- ' = 8, '--' = 12,
764 : //
765 : // ' ' = 0, ' ' = 1, '|' = 2, '|' = 3
766 : // | |
767 :
768 : static output_character crossings[4*4] = {
769 : 0x0000, 0x2577, 0x2575, 0x2502,
770 : 0x2576, 0x250C, 0x2514, 0x251C,
771 : 0x2574, 0x2510, 0x2518, 0x2524,
772 : 0x2500, 0x252C, 0x2534, 0x253C
773 : };
774 :
775 1058 : void tty_printer::end_page(int page_length)
776 : {
777 1058 : if (page_length % font::vert != 0)
778 0 : error("vertical position at end of page not multiple of vertical"
779 : " motion quantum");
780 1058 : int lines_per_page = page_length / font::vert;
781 : int last_line;
782 76770 : for (last_line = nlines; last_line > 0; last_line--)
783 76752 : if (lines[last_line - 1])
784 1040 : break;
785 : #if 0
786 : if (last_line > lines_per_page) {
787 : error("characters past last line discarded");
788 : do {
789 : --last_line;
790 : while (lines[last_line]) {
791 : tty_glyph *tem = lines[last_line];
792 : lines[last_line] = tem->next;
793 : delete tem;
794 : }
795 : } while (last_line > lines_per_page);
796 : }
797 : #endif
798 39734 : for (int i = 0; i < last_line; i++) {
799 38676 : tty_glyph *p = lines[i];
800 38676 : lines[i] = 0;
801 38676 : tty_glyph *g = 0;
802 1131652 : while (p) {
803 1092976 : tty_glyph *tem = p->next;
804 1092976 : p->next = g;
805 1092976 : g = p;
806 1092976 : p = tem;
807 : }
808 38676 : int hpos = 0;
809 : tty_glyph *nextp;
810 38676 : curr_fore_idx = DEFAULT_COLOR_IDX;
811 38676 : curr_back_idx = DEFAULT_COLOR_IDX;
812 38676 : is_underlining = false;
813 38676 : is_boldfacing = false;
814 1131652 : for (p = g; p; delete p, p = nextp) {
815 1092976 : nextp = p->next;
816 1092976 : if (p->mode & CU_MODE) {
817 0 : is_continuously_underlining = (p->code != 0);
818 0 : continue;
819 : }
820 1092976 : if (nextp && p->hpos == nextp->hpos) {
821 : // We expect HDRAW_MODE glyphs to always precede VDRAW_MODEs.
822 35974 : assert (!(p->draw_mode() == VDRAW_MODE
823 : && nextp->draw_mode() == HDRAW_MODE));
824 36302 : if (p->draw_mode() == HDRAW_MODE &&
825 328 : nextp->draw_mode() == VDRAW_MODE) {
826 273 : if (font::is_unicode)
827 185 : nextp->code =
828 185 : crossings[((p->mode & (START_LINE|END_LINE)) >> 4)
829 185 : + ((nextp->mode & (START_LINE|END_LINE)) >> 6)];
830 : else
831 88 : nextp->code = '+';
832 273 : continue;
833 : }
834 35701 : if (p->draw_mode() != 0 && p->draw_mode() == nextp->draw_mode())
835 : {
836 189 : nextp->code = p->code;
837 189 : continue;
838 : }
839 35512 : if (!want_glyph_composition_by_overstriking)
840 812 : continue;
841 : }
842 1091702 : if (hpos > p->hpos) {
843 16 : do {
844 54 : putchar('\b');
845 54 : hpos--;
846 54 : } while (hpos > p->hpos);
847 : }
848 : else {
849 1091664 : if (want_horizontal_tabs) {
850 : for (;;) {
851 49 : int next_tab_pos = ((hpos + TAB_WIDTH) / TAB_WIDTH) * TAB_WIDTH;
852 49 : if (next_tab_pos > p->hpos)
853 47 : break;
854 2 : if (is_continuously_underlining)
855 0 : make_underline(p->w);
856 2 : else if (!use_overstriking_drawing_scheme
857 2 : && is_underlining) {
858 0 : if (do_sgr_italics)
859 0 : putstring(SGR_NO_ITALIC);
860 0 : else if (do_reverse_video)
861 0 : putstring(SGR_NO_REVERSE);
862 : else
863 0 : putstring(SGR_NO_UNDERLINE);
864 0 : is_underlining = false;
865 : }
866 2 : if ((next_tab_pos - hpos) > 1)
867 1 : putchar('\t');
868 : else
869 1 : putchar(' ');
870 2 : hpos = next_tab_pos;
871 2 : }
872 : }
873 1578540 : for (; hpos < p->hpos; hpos++) {
874 486876 : if (is_continuously_underlining)
875 0 : make_underline(p->w);
876 486876 : else if (!use_overstriking_drawing_scheme && is_underlining) {
877 6822 : if (do_sgr_italics)
878 0 : putstring(SGR_NO_ITALIC);
879 6822 : else if (do_reverse_video)
880 0 : putstring(SGR_NO_REVERSE);
881 : else
882 6822 : putstring(SGR_NO_UNDERLINE);
883 6822 : is_underlining = false;
884 : }
885 486876 : putchar(' ');
886 : }
887 : }
888 1091702 : assert(hpos == p->hpos);
889 1091702 : if (p->mode & COLOR_CHANGE) {
890 1184 : if (!use_overstriking_drawing_scheme) {
891 1126 : if (p->fore_color_idx != curr_fore_idx) {
892 0 : put_color(p->fore_color_idx, 0);
893 0 : curr_fore_idx = p->fore_color_idx;
894 : }
895 1126 : if (p->back_color_idx != curr_back_idx) {
896 0 : put_color(p->back_color_idx, 1);
897 0 : curr_back_idx = p->back_color_idx;
898 : }
899 : }
900 1184 : continue;
901 : }
902 1090518 : if (p->mode & UNDERLINE_MODE)
903 74987 : make_underline(p->w);
904 1015531 : else if (!use_overstriking_drawing_scheme && is_underlining) {
905 3879 : if (do_sgr_italics)
906 0 : putstring(SGR_NO_ITALIC);
907 3879 : else if (do_reverse_video)
908 0 : putstring(SGR_NO_REVERSE);
909 : else
910 3879 : putstring(SGR_NO_UNDERLINE);
911 3879 : is_underlining = false;
912 : }
913 1090518 : if (p->mode & BOLD_MODE)
914 57715 : make_bold(p->code, p->w);
915 1032803 : else if (!use_overstriking_drawing_scheme && is_boldfacing) {
916 10432 : putstring(SGR_NO_BOLD);
917 10432 : is_boldfacing = false;
918 : }
919 1090518 : if (!use_overstriking_drawing_scheme) {
920 995318 : if (p->fore_color_idx != curr_fore_idx) {
921 0 : put_color(p->fore_color_idx, 0);
922 0 : curr_fore_idx = p->fore_color_idx;
923 : }
924 995318 : if (p->back_color_idx != curr_back_idx) {
925 0 : put_color(p->back_color_idx, 1);
926 0 : curr_back_idx = p->back_color_idx;
927 : }
928 : }
929 1090518 : put_char(p->code);
930 1090518 : hpos += p->w / font::hor;
931 : }
932 38676 : if (!use_overstriking_drawing_scheme
933 28622 : && (is_boldfacing || is_underlining
934 25473 : || curr_fore_idx != DEFAULT_COLOR_IDX
935 25473 : || curr_back_idx != DEFAULT_COLOR_IDX))
936 3149 : putstring(SGR_DEFAULT);
937 38676 : putchar('\n');
938 : }
939 1058 : if (want_form_feeds) {
940 0 : if (last_line < lines_per_page)
941 0 : putchar('\f');
942 : }
943 : else {
944 45645 : for (; last_line < lines_per_page; last_line++)
945 44587 : putchar('\n');
946 : }
947 1058 : }
948 :
949 1300 : font *tty_printer::make_font(const char *nm)
950 : {
951 1300 : return tty_font::load_tty_font(nm);
952 : }
953 :
954 944 : printer *make_printer()
955 : {
956 944 : return new tty_printer();
957 : }
958 :
959 991 : static void update_options()
960 : {
961 991 : if (use_overstriking_drawing_scheme) {
962 422 : do_sgr_italics = false;
963 422 : do_reverse_video = false;
964 422 : bold_underline_mode = bold_underline_mode_option;
965 422 : do_bold = want_emboldening_by_overstriking;
966 422 : do_underline = want_italics_by_underlining;
967 : }
968 : else {
969 569 : do_sgr_italics = want_sgr_italics;
970 569 : do_reverse_video = want_reverse_video_for_italics;
971 569 : bold_underline_mode = BOLD_MODE|UNDERLINE_MODE;
972 569 : do_bold = true;
973 569 : do_underline = true;
974 : }
975 991 : }
976 :
977 993 : int main(int argc, char **argv)
978 : {
979 993 : program_name = argv[0];
980 : static char stderr_buf[BUFSIZ];
981 993 : if (getenv("GROFF_NO_SGR"))
982 0 : use_overstriking_drawing_scheme = true;
983 993 : setbuf(stderr, stderr_buf);
984 993 : setlocale(LC_CTYPE, "");
985 : int c;
986 : static const struct option long_options[] = {
987 : { "help", no_argument, 0 /* nullptr */, CHAR_MAX + 1 },
988 : { "version", no_argument, 0 /* nullptr */, 'v' },
989 : { 0 /* nullptr */, 0, 0 /* nullptr */, 0 }
990 : };
991 2714 : while ((c = getopt_long(argc, argv, ":bBcdfF:hiI:ortuUv",
992 : long_options, 0 /* nullptr */))
993 2714 : != EOF)
994 1723 : switch (c) {
995 2 : case 'v':
996 2 : printf("GNU grotty (groff) version %s\n", Version_string);
997 2 : exit(EXIT_SUCCESS);
998 : break;
999 0 : case 'i':
1000 : // Use italic font instead of underlining.
1001 0 : want_sgr_italics = true;
1002 0 : break;
1003 0 : case 'I':
1004 : // ignore include search path
1005 0 : break;
1006 422 : case 'b':
1007 : // Do not embolden by overstriking.
1008 422 : want_emboldening_by_overstriking = false;
1009 422 : break;
1010 422 : case 'c':
1011 : // Use old scheme for emboldening and underline.
1012 422 : use_overstriking_drawing_scheme = true;
1013 422 : break;
1014 422 : case 'u':
1015 : // Do not underline.
1016 422 : want_italics_by_underlining = false;
1017 422 : break;
1018 422 : case 'o':
1019 : // Do not overstrike (other than emboldening and underlining).
1020 422 : want_glyph_composition_by_overstriking = false;
1021 422 : break;
1022 0 : case 'r':
1023 : // Use reverse mode instead of underlining.
1024 0 : want_reverse_video_for_italics = true;
1025 0 : break;
1026 0 : case 'B':
1027 : // Do bold-underlining as bold.
1028 0 : bold_underline_mode_option = BOLD_MODE;
1029 0 : break;
1030 0 : case 'U':
1031 : // Do bold-underlining as underlining.
1032 0 : bold_underline_mode_option = UNDERLINE_MODE;
1033 0 : break;
1034 1 : case 'h':
1035 : // Use horizontal tabs.
1036 1 : want_horizontal_tabs = true;
1037 1 : break;
1038 0 : case 'f':
1039 0 : want_form_feeds = true;
1040 0 : break;
1041 32 : case 'F':
1042 32 : font::command_line_font_dir(optarg);
1043 32 : break;
1044 0 : case 'd':
1045 : // Ignore \D commands.
1046 0 : allow_drawing_commands = false;
1047 0 : break;
1048 0 : case 't':
1049 : // Use SGR 38 and 48 sequences instead of SGR 30-37 and 40-47.
1050 0 : want_sgr_truecolor = true;
1051 0 : break;
1052 0 : case CHAR_MAX + 1: // --help
1053 0 : usage(stdout);
1054 0 : exit(EXIT_SUCCESS);
1055 : break;
1056 0 : case '?':
1057 0 : if (optopt != 0)
1058 0 : error("unrecognized command-line option '%1'", char(optopt));
1059 : else
1060 0 : error("unrecognized command-line option '%1'",
1061 0 : argv[(optind - 1)]);
1062 0 : usage(stderr);
1063 0 : exit(2);
1064 : break;
1065 0 : case ':':
1066 0 : error("command-line option '%1' requires an argument",
1067 0 : char(optopt));
1068 0 : usage(stderr);
1069 0 : exit(2);
1070 : break;
1071 0 : default:
1072 0 : assert(0 == "unhandled getopt_long return value");
1073 : }
1074 991 : update_options();
1075 991 : if (optind >= argc)
1076 991 : do_file("-");
1077 : else {
1078 0 : for (int i = optind; i < argc; i++)
1079 0 : do_file(argv[i]);
1080 : }
1081 991 : return 0;
1082 : }
1083 :
1084 0 : static void usage(FILE *stream)
1085 : {
1086 0 : fprintf(stream,
1087 : "usage: %s [-dfhot] [-i|-r] [-F font-directory] [file ...]\n"
1088 : "usage: %s -c [-bBdfhouU] [-F font-directory] [file ...]\n"
1089 : "usage: %s {-v | --version}\n"
1090 : "usage: %s --help\n",
1091 : program_name, program_name, program_name, program_name);
1092 0 : if (stdout == stream)
1093 0 : fputs("\n"
1094 : "Translate the output of troff(1) into a form suitable for\n"
1095 : "typewriter‐like devices, including terminal emulators. See the\n"
1096 : "grotty(1) manual page.\n",
1097 : stream);
1098 0 : }
1099 :
1100 : // Local Variables:
1101 : // fill-column: 72
1102 : // mode: C++
1103 : // End:
1104 : // vim: set cindent noexpandtab shiftwidth=2 textwidth=72:
|