1 /* PSPP - a program for statistical analysis.
2 Copyright (C) 1997-9, 2000, 2007, 2009, 2010, 2011, 2012, 2013, 2014 Free Software Foundation, Inc.
3
4 This program is free software: you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation, either version 3 of the License, or
7 (at your option) any later version.
8
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
13
14 You should have received a copy of the GNU General Public License
15 along with this program. If not, see <http://www.gnu.org/licenses/>. */
16
17 #include <config.h>
18
19 #include <ctype.h>
20 #include <errno.h>
21 #include <limits.h>
22 #include <signal.h>
23 #include <stdint.h>
24 #include <stdlib.h>
25 #include <unilbrk.h>
26 #include <unistd.h>
27 #include <unistr.h>
28 #include <uniwidth.h>
29
30 #ifdef HAVE_TERMIOS_H
31 # include <termios.h>
32 #endif
33
34 #ifdef GWINSZ_IN_SYS_IOCTL
35 # include <sys/ioctl.h>
36 #endif
37
38 #include "data/file-name.h"
39 #include "data/file-handle-def.h"
40 #include "data/settings.h"
41 #include "libpspp/assertion.h"
42 #include "libpspp/cast.h"
43 #include "libpspp/compiler.h"
44 #include "libpspp/message.h"
45 #include "libpspp/start-date.h"
46 #include "libpspp/string-map.h"
47 #include "libpspp/u8-line.h"
48 #include "libpspp/version.h"
49 #include "output/ascii.h"
50 #include "output/cairo.h"
51 #include "output/chart-item-provider.h"
52 #include "output/driver-provider.h"
53 #include "output/message-item.h"
54 #include "output/options.h"
55 #include "output/render.h"
56 #include "output/table-item.h"
57 #include "output/text-item.h"
58
59 #include "gl/minmax.h"
60 #include "gl/xalloc.h"
61 #include "gl/xsize.h"
62
63 #include "gettext.h"
64 #define _(msgid) gettext (msgid)
65
66 /* This file uses TABLE_HORZ and TABLE_VERT enough to warrant abbreviating. */
67 #define H TABLE_HORZ
68 #define V TABLE_VERT
69
70 enum
71 {
72 ASCII_LINE_NONE,
73 ASCII_LINE_SINGLE,
74 ASCII_LINE_DOUBLE,
75 ASCII_N_LINES
76 };
77
78 #define N_BOX (ASCII_N_LINES * ASCII_N_LINES \
79 * ASCII_N_LINES * ASCII_N_LINES)
80
81 static const ucs4_t ascii_box_chars[N_BOX] =
82 {
83 ' ', '|', '#',
84 '-', '+', '#',
85 '=', '#', '#',
86 '|', '|', '#',
87 '+', '+', '#',
88 '#', '#', '#',
89 '#', '#', '#',
90 '#', '#', '#',
91 '#', '#', '#',
92 '-', '+', '#',
93 '-', '+', '#',
94 '#', '#', '#',
95 '+', '+', '#',
96 '+', '+', '#',
97 '#', '#', '#',
98 '#', '#', '#',
99 '#', '#', '#',
100 '#', '#', '#',
101 '=', '#', '#',
102 '#', '#', '#',
103 '=', '#', '#',
104 '#', '#', '#',
105 '#', '#', '#',
106 '#', '#', '#',
107 '#', '#', '#',
108 '#', '#', '#',
109 '#', '#', '#',
110 };
111
112 static const ucs4_t unicode_box_chars[N_BOX] =
113 {
114 0x0020, 0x2575, 0x2551,
115 0x2574, 0x256f, 0x255c,
116 0x2550, 0x255b, 0x255d,
117 0x2577, 0x2502, 0x2551,
118 0x256e, 0x2524, 0x2562,
119 0x2555, 0x2561, 0x2563,
120 0x2551, 0x2551, 0x2551,
121 0x2556, 0x2562, 0x2562,
122 0x2557, 0x2563, 0x2563,
123 0x2576, 0x2570, 0x2559,
124 0x2500, 0x2534, 0x2568,
125 0x2550, 0x2567, 0x2569,
126 0x256d, 0x251c, 0x255f,
127 0x252c, 0x253c, 0x256a,
128 0x2564, 0x256a, 0x256c,
129 0x2553, 0x255f, 0x255f,
130 0x2565, 0x256b, 0x256b,
131 0x2566, 0x256c, 0x256c,
132 0x2550, 0x2558, 0x255a,
133 0x2550, 0x2567, 0x2569,
134 0x2550, 0x2567, 0x2569,
135 0x2552, 0x255e, 0x2560,
136 0x2564, 0x256a, 0x256c,
137 0x2564, 0x256a, 0x256c,
138 0x2554, 0x2560, 0x2560,
139 0x2560, 0x256c, 0x256c,
140 0x2566, 0x256c, 0x256c,
141 };
142
143 static int
ascii_line_from_render_line(int render_line)144 ascii_line_from_render_line (int render_line)
145 {
146 switch (render_line)
147 {
148 case RENDER_LINE_NONE:
149 return ASCII_LINE_NONE;
150
151 case RENDER_LINE_SINGLE:
152 case RENDER_LINE_DASHED:
153 case RENDER_LINE_THICK:
154 case RENDER_LINE_THIN:
155 return ASCII_LINE_SINGLE;
156
157 case RENDER_LINE_DOUBLE:
158 return ASCII_LINE_DOUBLE;
159
160 default:
161 return ASCII_LINE_NONE;
162 }
163
164 }
165
166 static int
make_box_index(int left_,int right_,int top_,int bottom_)167 make_box_index (int left_, int right_, int top_, int bottom_)
168 {
169 bool rtl = render_direction_rtl ();
170 int left = ascii_line_from_render_line (rtl ? right_ : left_);
171 int right = ascii_line_from_render_line (rtl ? left_ : right_);
172 int top = ascii_line_from_render_line (top_);
173 int bottom = ascii_line_from_render_line (bottom_);
174
175 int idx = right;
176 idx = idx * ASCII_N_LINES + bottom;
177 idx = idx * ASCII_N_LINES + left;
178 idx = idx * ASCII_N_LINES + top;
179 return idx;
180 }
181
182 /* ASCII output driver. */
183 struct ascii_driver
184 {
185 struct output_driver driver;
186
187 /* User parameters. */
188 bool append; /* Append if output file already exists? */
189 bool emphasis; /* Enable bold and underline in output? */
190 char *chart_file_name; /* Name of files used for charts. */
191
192 #ifdef HAVE_CAIRO
193 /* Colours for charts */
194 struct cell_color fg;
195 struct cell_color bg;
196 #endif
197
198 /* How the page width is determined: */
199 enum {
200 FIXED_WIDTH, /* Specified by configuration. */
201 VIEW_WIDTH, /* From SET WIDTH. */
202 TERMINAL_WIDTH /* From the terminal's width. */
203 } width_mode;
204 int width; /* Page width. */
205
206 int min_hbreak; /* Min cell size to break across pages. */
207
208 const ucs4_t *box; /* Line & box drawing characters. */
209
210 /* Internal state. */
211 struct file_handle *handle;
212 FILE *file; /* Output file. */
213 bool error; /* Output error? */
214 struct u8_line *lines; /* Page content. */
215 int allocated_lines; /* Number of lines allocated. */
216 int chart_cnt; /* Number of charts so far. */
217 int object_cnt; /* Number of objects so far. */
218 struct render_params params;
219 };
220
221 static const struct output_driver_class ascii_driver_class;
222
223 static void ascii_submit (struct output_driver *, const struct output_item *);
224
225 static int get_terminal_width (void);
226
227 static bool update_page_size (struct ascii_driver *, bool issue_error);
228 static int parse_page_size (struct driver_option *);
229
230 static void ascii_draw_line (void *, int bb[TABLE_N_AXES][2],
231 enum render_line_style styles[TABLE_N_AXES][2],
232 struct cell_color colors[TABLE_N_AXES][2]);
233 static void ascii_measure_cell_width (void *, const struct table_cell *,
234 int *min, int *max);
235 static int ascii_measure_cell_height (void *, const struct table_cell *,
236 int width);
237 static void ascii_draw_cell (void *, const struct table_cell *, int color_idx,
238 int bb[TABLE_N_AXES][2],
239 int spill[TABLE_N_AXES][2],
240 int clip[TABLE_N_AXES][2]);
241
242 static struct ascii_driver *
ascii_driver_cast(struct output_driver * driver)243 ascii_driver_cast (struct output_driver *driver)
244 {
245 assert (driver->class == &ascii_driver_class);
246 return UP_CAST (driver, struct ascii_driver, driver);
247 }
248
249 static struct driver_option *
opt(struct output_driver * d,struct string_map * options,const char * key,const char * default_value)250 opt (struct output_driver *d, struct string_map *options, const char *key,
251 const char *default_value)
252 {
253 return driver_option_get (d, options, key, default_value);
254 }
255
256 /* Return true iff the terminal appears to be an xterm with
257 UTF-8 capabilities */
258 static bool
term_is_utf8_xterm(void)259 term_is_utf8_xterm (void)
260 {
261 const char *term = getenv ("TERM");
262 const char *xterm_locale = getenv ("XTERM_LOCAL");
263 return (term && xterm_locale
264 && !strcmp (term, "xterm")
265 && (strcasestr (xterm_locale, "utf8")
266 || strcasestr (xterm_locale, "utf-8")));
267 }
268
269 static struct output_driver *
ascii_create(struct file_handle * fh,enum settings_output_devices device_type,struct string_map * o)270 ascii_create (struct file_handle *fh, enum settings_output_devices device_type,
271 struct string_map *o)
272 {
273 enum { BOX_ASCII, BOX_UNICODE } box;
274 struct output_driver *d;
275 struct ascii_driver *a;
276
277 a = xzalloc (sizeof *a);
278 d = &a->driver;
279 output_driver_init (&a->driver, &ascii_driver_class, fh_get_file_name (fh), device_type);
280 a->append = parse_boolean (opt (d, o, "append", "false"));
281 a->emphasis = parse_boolean (opt (d, o, "emphasis", "false"));
282
283 a->chart_file_name = parse_chart_file_name (opt (d, o, "charts", fh_get_file_name (fh)));
284 a->handle = fh;
285
286
287 bool terminal = !strcmp (fh_get_file_name (fh), "-") && isatty (1);
288 a->width = parse_page_size (opt (d, o, "width", "-1"));
289 a->width_mode = (a->width > 0 ? FIXED_WIDTH
290 : terminal ? TERMINAL_WIDTH
291 : VIEW_WIDTH);
292 a->min_hbreak = parse_int (opt (d, o, "min-hbreak", "-1"), -1, INT_MAX);
293
294 #ifdef HAVE_CAIRO
295 parse_color (d, o, "background-color", "#FFFFFFFFFFFF", &a->bg);
296 parse_color (d, o, "foreground-color", "#000000000000", &a->fg);
297 #endif
298
299 const char *default_box = (terminal && (!strcmp (locale_charset (), "UTF-8")
300 || term_is_utf8_xterm ())
301 ? "unicode" : "ascii");
302 box = parse_enum (opt (d, o, "box", default_box),
303 "ascii", BOX_ASCII,
304 "unicode", BOX_UNICODE,
305 NULL_SENTINEL);
306 a->box = box == BOX_ASCII ? ascii_box_chars : unicode_box_chars;
307
308 a->file = NULL;
309 a->error = false;
310 a->lines = NULL;
311 a->allocated_lines = 0;
312 a->chart_cnt = 0;
313 a->object_cnt = 0;
314
315 a->params.draw_line = ascii_draw_line;
316 a->params.measure_cell_width = ascii_measure_cell_width;
317 a->params.measure_cell_height = ascii_measure_cell_height;
318 a->params.adjust_break = NULL;
319 a->params.draw_cell = ascii_draw_cell;
320 a->params.aux = a;
321 a->params.size[H] = a->width;
322 a->params.size[V] = INT_MAX;
323 a->params.font_size[H] = 1;
324 a->params.font_size[V] = 1;
325 for (int i = 0; i < RENDER_N_LINES; i++)
326 {
327 int width = i == RENDER_LINE_NONE ? 0 : 1;
328 a->params.line_widths[H][i] = width;
329 a->params.line_widths[V][i] = width;
330 }
331 a->params.supports_margins = false;
332 a->params.rtl = render_direction_rtl ();
333
334 if (!update_page_size (a, true))
335 goto error;
336
337 a->file = fn_open (a->handle, a->append ? "a" : "w");
338 if (!a->file)
339 {
340 msg_error (errno, _("ascii: opening output file `%s'"),
341 fh_get_file_name (a->handle));
342 goto error;
343 }
344
345 return d;
346
347 error:
348 output_driver_destroy (d);
349 return NULL;
350 }
351
352 static int
parse_page_size(struct driver_option * option)353 parse_page_size (struct driver_option *option)
354 {
355 int dim = atol (option->default_value);
356
357 if (option->value != NULL)
358 {
359 if (!strcmp (option->value, "auto"))
360 dim = -1;
361 else
362 {
363 int value;
364 char *tail;
365
366 errno = 0;
367 value = strtol (option->value, &tail, 0);
368 if (value >= 1 && errno != ERANGE && *tail == '\0')
369 dim = value;
370 else
371 msg (MW, _("%s: %s must be positive integer or `auto'"),
372 option->driver_name, option->name);
373 }
374 }
375
376 driver_option_destroy (option);
377
378 return dim;
379 }
380
381 /* Re-calculates the page width based on settings, margins, and, if "auto" is
382 set, the size of the user's terminal window or GUI output window. */
383 static bool
update_page_size(struct ascii_driver * a,bool issue_error)384 update_page_size (struct ascii_driver *a, bool issue_error)
385 {
386 enum { MIN_WIDTH = 6 };
387
388 int want_width = (a->width_mode == VIEW_WIDTH ? settings_get_viewwidth ()
389 : a->width_mode == TERMINAL_WIDTH ? get_terminal_width ()
390 : a->width);
391 bool ok = want_width >= MIN_WIDTH;
392 if (!ok && issue_error)
393 msg (ME, _("ascii: page must be at least %d characters wide, but "
394 "as configured is only %d characters"),
395 MIN_WIDTH, want_width);
396
397 a->width = ok ? want_width : MIN_WIDTH;
398 a->params.size[H] = a->width;
399 a->params.min_break[H] = a->min_hbreak >= 0 ? a->min_hbreak : a->width / 2;
400
401 return ok;
402 }
403
404 static void
ascii_destroy(struct output_driver * driver)405 ascii_destroy (struct output_driver *driver)
406 {
407 struct ascii_driver *a = ascii_driver_cast (driver);
408 int i;
409
410 if (a->file != NULL)
411 fn_close (a->handle, a->file);
412 fh_unref (a->handle);
413 free (a->chart_file_name);
414 for (i = 0; i < a->allocated_lines; i++)
415 u8_line_destroy (&a->lines[i]);
416 free (a->lines);
417 free (a);
418 }
419
420 static void
ascii_flush(struct output_driver * driver)421 ascii_flush (struct output_driver *driver)
422 {
423 struct ascii_driver *a = ascii_driver_cast (driver);
424 if (a->file)
425 fflush (a->file);
426 }
427
428 static void
ascii_output_lines(struct ascii_driver * a,size_t n_lines)429 ascii_output_lines (struct ascii_driver *a, size_t n_lines)
430 {
431 for (size_t y = 0; y < n_lines; y++)
432 {
433 if (y < a->allocated_lines)
434 {
435 struct u8_line *line = &a->lines[y];
436
437 while (ds_chomp_byte (&line->s, ' '))
438 continue;
439 fwrite (ds_data (&line->s), 1, ds_length (&line->s), a->file);
440 u8_line_clear (&a->lines[y]);
441 }
442 putc ('\n', a->file);
443 }
444 }
445
446 static void
ascii_output_table_item(struct ascii_driver * a,const struct table_item * table_item)447 ascii_output_table_item (struct ascii_driver *a,
448 const struct table_item *table_item)
449 {
450 struct render_pager *p;
451
452 update_page_size (a, false);
453
454 if (a->object_cnt++)
455 putc ('\n', a->file);
456
457 p = render_pager_create (&a->params, table_item);
458 for (int i = 0; render_pager_has_next (p); i++)
459 {
460 if (i)
461 putc ('\n', a->file);
462 ascii_output_lines (a, render_pager_draw_next (p, INT_MAX));
463 }
464 render_pager_destroy (p);
465 }
466
467 static void
ascii_output_table_item_unref(struct ascii_driver * a,struct table_item * table_item)468 ascii_output_table_item_unref (struct ascii_driver *a,
469 struct table_item *table_item)
470 {
471 ascii_output_table_item (a, table_item);
472 table_item_unref (table_item);
473 }
474
475 static void
ascii_output_text(struct ascii_driver * a,const char * text)476 ascii_output_text (struct ascii_driver *a, const char *text)
477 {
478 ascii_output_table_item_unref (
479 a, table_item_create (table_from_string (text), NULL, NULL));
480 }
481
482 static void
ascii_submit(struct output_driver * driver,const struct output_item * output_item)483 ascii_submit (struct output_driver *driver,
484 const struct output_item *output_item)
485 {
486 struct ascii_driver *a = ascii_driver_cast (driver);
487
488 if (a->error)
489 return;
490
491 if (is_table_item (output_item))
492 ascii_output_table_item (a, to_table_item (output_item));
493 #ifdef HAVE_CAIRO
494 else if (is_chart_item (output_item) && a->chart_file_name != NULL)
495 {
496 struct chart_item *chart_item = to_chart_item (output_item);
497 char *file_name;
498
499 file_name = xr_draw_png_chart (chart_item, a->chart_file_name,
500 ++a->chart_cnt,
501 &a->fg,
502 &a->bg);
503 if (file_name != NULL)
504 {
505 struct text_item *text_item;
506
507 text_item = text_item_create_format (
508 TEXT_ITEM_LOG, _("See %s for a chart."), file_name);
509
510 ascii_submit (driver, &text_item->output_item);
511 text_item_unref (text_item);
512 free (file_name);
513 }
514 }
515 #endif /* HAVE_CAIRO */
516 else if (is_text_item (output_item))
517 {
518 const struct text_item *text_item = to_text_item (output_item);
519 enum text_item_type type = text_item_get_type (text_item);
520
521 if (type != TEXT_ITEM_PAGE_TITLE && type != TEXT_ITEM_EJECT_PAGE)
522 ascii_output_table_item_unref (
523 a, text_item_to_table_item (text_item_ref (text_item)));
524 }
525 else if (is_message_item (output_item))
526 {
527 const struct message_item *message_item = to_message_item (output_item);
528 char *s = msg_to_string (message_item_get_msg (message_item));
529 ascii_output_text (a, s);
530 free (s);
531 }
532 }
533
534 const struct output_driver_factory txt_driver_factory =
535 { "txt", "-", ascii_create };
536 const struct output_driver_factory list_driver_factory =
537 { "list", "-", ascii_create };
538
539 static const struct output_driver_class ascii_driver_class =
540 {
541 "text",
542 ascii_destroy,
543 ascii_submit,
544 ascii_flush,
545 };
546
547 static char *ascii_reserve (struct ascii_driver *, int y, int x0, int x1,
548 int n);
549 static void ascii_layout_cell (struct ascii_driver *,
550 const struct table_cell *,
551 int bb[TABLE_N_AXES][2],
552 int clip[TABLE_N_AXES][2],
553 int *width, int *height);
554
555 static void
ascii_draw_line(void * a_,int bb[TABLE_N_AXES][2],enum render_line_style styles[TABLE_N_AXES][2],struct cell_color colors[TABLE_N_AXES][2]UNUSED)556 ascii_draw_line (void *a_, int bb[TABLE_N_AXES][2],
557 enum render_line_style styles[TABLE_N_AXES][2],
558 struct cell_color colors[TABLE_N_AXES][2] UNUSED)
559 {
560 struct ascii_driver *a = a_;
561 char mbchar[6];
562 int x0, y0, x1, y1;
563 ucs4_t uc;
564 int mblen;
565 int x, y;
566
567 /* Clip to the page. */
568 x0 = MAX (bb[H][0], 0);
569 y0 = MAX (bb[V][0], 0);
570 x1 = MIN (bb[H][1], a->width);
571 y1 = bb[V][1];
572 if (x1 <= 0 || y1 <= 0 || x0 >= a->width)
573 return;
574
575 /* Draw. */
576 uc = a->box[make_box_index (styles[V][0], styles[V][1],
577 styles[H][0], styles[H][1])];
578 mblen = u8_uctomb (CHAR_CAST (uint8_t *, mbchar), uc, 6);
579 for (y = y0; y < y1; y++)
580 {
581 char *p = ascii_reserve (a, y, x0, x1, mblen * (x1 - x0));
582 for (x = x0; x < x1; x++)
583 {
584 memcpy (p, mbchar, mblen);
585 p += mblen;
586 }
587 }
588 }
589
590 static void
ascii_measure_cell_width(void * a_,const struct table_cell * cell,int * min_width,int * max_width)591 ascii_measure_cell_width (void *a_, const struct table_cell *cell,
592 int *min_width, int *max_width)
593 {
594 struct ascii_driver *a = a_;
595 int bb[TABLE_N_AXES][2];
596 int clip[TABLE_N_AXES][2];
597 int h;
598
599 bb[H][0] = 0;
600 bb[H][1] = INT_MAX;
601 bb[V][0] = 0;
602 bb[V][1] = INT_MAX;
603 clip[H][0] = clip[H][1] = clip[V][0] = clip[V][1] = 0;
604 ascii_layout_cell (a, cell, bb, clip, max_width, &h);
605
606 if (cell->n_footnotes || strchr (cell->text, ' ')
607 || cell->n_subscripts || cell->superscript)
608 {
609 bb[H][1] = 1;
610 ascii_layout_cell (a, cell, bb, clip, min_width, &h);
611 }
612 else
613 *min_width = *max_width;
614 }
615
616 static int
ascii_measure_cell_height(void * a_,const struct table_cell * cell,int width)617 ascii_measure_cell_height (void *a_, const struct table_cell *cell, int width)
618 {
619 struct ascii_driver *a = a_;
620 int bb[TABLE_N_AXES][2];
621 int clip[TABLE_N_AXES][2];
622 int w, h;
623
624 bb[H][0] = 0;
625 bb[H][1] = width;
626 bb[V][0] = 0;
627 bb[V][1] = INT_MAX;
628 clip[H][0] = clip[H][1] = clip[V][0] = clip[V][1] = 0;
629 ascii_layout_cell (a, cell, bb, clip, &w, &h);
630 return h;
631 }
632
633 static void
ascii_draw_cell(void * a_,const struct table_cell * cell,int color_idx UNUSED,int bb[TABLE_N_AXES][2],int spill[TABLE_N_AXES][2]UNUSED,int clip[TABLE_N_AXES][2])634 ascii_draw_cell (void *a_, const struct table_cell *cell, int color_idx UNUSED,
635 int bb[TABLE_N_AXES][2],
636 int spill[TABLE_N_AXES][2] UNUSED,
637 int clip[TABLE_N_AXES][2])
638 {
639 struct ascii_driver *a = a_;
640 int w, h;
641
642 ascii_layout_cell (a, cell, bb, clip, &w, &h);
643 }
644
645 static char *
ascii_reserve(struct ascii_driver * a,int y,int x0,int x1,int n)646 ascii_reserve (struct ascii_driver *a, int y, int x0, int x1, int n)
647 {
648 if (y >= a->allocated_lines)
649 {
650 size_t new_alloc = MAX (25, a->allocated_lines);
651 while (new_alloc <= y)
652 new_alloc = xtimes (new_alloc, 2);
653 a->lines = xnrealloc (a->lines, new_alloc, sizeof *a->lines);
654 for (size_t i = a->allocated_lines; i < new_alloc; i++)
655 u8_line_init (&a->lines[i]);
656 a->allocated_lines = new_alloc;
657 }
658 return u8_line_reserve (&a->lines[y], x0, x1, n);
659 }
660
661 static void
text_draw(struct ascii_driver * a,enum table_halign halign,int options,bool bold,bool underline,int bb[TABLE_N_AXES][2],int clip[TABLE_N_AXES][2],int y,const uint8_t * string,int n,size_t width)662 text_draw (struct ascii_driver *a, enum table_halign halign, int options,
663 bool bold, bool underline,
664 int bb[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2],
665 int y, const uint8_t *string, int n, size_t width)
666 {
667 int x0 = MAX (0, clip[H][0]);
668 int y0 = MAX (0, clip[V][0]);
669 int x1 = MIN (a->width, clip[H][1]);
670 int y1 = clip[V][1];
671 int x;
672
673 if (y < y0 || y >= y1)
674 return;
675
676 switch (table_halign_interpret (halign, options & TAB_NUMERIC))
677 {
678 case TABLE_HALIGN_LEFT:
679 x = bb[H][0];
680 break;
681 case TABLE_HALIGN_CENTER:
682 x = (bb[H][0] + bb[H][1] - width + 1) / 2;
683 break;
684 case TABLE_HALIGN_RIGHT:
685 case TABLE_HALIGN_DECIMAL:
686 x = bb[H][1] - width;
687 break;
688 default:
689 NOT_REACHED ();
690 }
691 if (x >= x1)
692 return;
693
694 while (x < x0)
695 {
696 ucs4_t uc;
697 int mblen;
698 int w;
699
700 if (n == 0)
701 return;
702 mblen = u8_mbtouc (&uc, string, n);
703
704 string += mblen;
705 n -= mblen;
706
707 w = uc_width (uc, "UTF-8");
708 if (w > 0)
709 {
710 x += w;
711 width -= w;
712 }
713 }
714 if (n == 0)
715 return;
716
717 if (x + width > x1)
718 {
719 int ofs;
720
721 ofs = width = 0;
722 for (ofs = 0; ofs < n;)
723 {
724 ucs4_t uc;
725 int mblen;
726 int w;
727
728 mblen = u8_mbtouc (&uc, string + ofs, n - ofs);
729
730 w = uc_width (uc, "UTF-8");
731 if (w > 0)
732 {
733 if (width + w > x1 - x)
734 break;
735 width += w;
736 }
737 ofs += mblen;
738 }
739 n = ofs;
740 if (n == 0)
741 return;
742 }
743
744 if (!a->emphasis || (!bold && !underline))
745 memcpy (ascii_reserve (a, y, x, x + width, n), string, n);
746 else
747 {
748 size_t n_out;
749 size_t ofs;
750 char *out;
751 int mblen;
752
753 /* First figure out how many bytes need to be inserted. */
754 n_out = n;
755 for (ofs = 0; ofs < n; ofs += mblen)
756 {
757 ucs4_t uc;
758 int w;
759
760 mblen = u8_mbtouc (&uc, string + ofs, n - ofs);
761 w = uc_width (uc, "UTF-8");
762
763 if (w > 0)
764 {
765 if (bold)
766 n_out += 1 + mblen;
767 if (underline)
768 n_out += 2;
769 }
770 }
771
772 /* Then insert them. */
773 out = ascii_reserve (a, y, x, x + width, n_out);
774 for (ofs = 0; ofs < n; ofs += mblen)
775 {
776 ucs4_t uc;
777 int w;
778
779 mblen = u8_mbtouc (&uc, string + ofs, n - ofs);
780 w = uc_width (uc, "UTF-8");
781
782 if (w > 0)
783 {
784 if (bold)
785 {
786 out = mempcpy (out, string + ofs, mblen);
787 *out++ = '\b';
788 }
789 if (underline)
790 {
791 *out++ = '_';
792 *out++ = '\b';
793 }
794 }
795 out = mempcpy (out, string + ofs, mblen);
796 }
797 }
798 }
799
800 static char *
add_markers(const char * text,const struct table_cell * cell)801 add_markers (const char *text, const struct table_cell *cell)
802 {
803 struct string s = DS_EMPTY_INITIALIZER;
804 ds_put_cstr (&s, text);
805 for (size_t i = 0; i < cell->n_subscripts; i++)
806 ds_put_format (&s, "%c%s", i ? ',' : '_', cell->subscripts[i]);
807 if (cell->superscript)
808 ds_put_format (&s, "^%s", cell->superscript);
809 for (size_t i = 0; i < cell->n_footnotes; i++)
810 ds_put_format (&s, "[%s]", cell->footnotes[i]->marker);
811 return ds_steal_cstr (&s);
812 }
813
814 static void
ascii_layout_cell(struct ascii_driver * a,const struct table_cell * cell,int bb[TABLE_N_AXES][2],int clip[TABLE_N_AXES][2],int * widthp,int * heightp)815 ascii_layout_cell (struct ascii_driver *a, const struct table_cell *cell,
816 int bb[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2],
817 int *widthp, int *heightp)
818 {
819 *widthp = 0;
820 *heightp = 0;
821
822 /* Get the basic textual contents. */
823 const char *plain_text = (cell->options & TAB_MARKUP
824 ? output_get_text_from_markup (cell->text)
825 : cell->text);
826
827 /* Append footnotes, subscripts, superscript if any. */
828 const char *text;
829 if (cell->n_footnotes || cell->n_subscripts || cell->superscript)
830 {
831 text = add_markers (plain_text, cell);
832 if (plain_text != cell->text)
833 free (CONST_CAST (char *, plain_text));
834 }
835 else
836 text = plain_text;
837
838 /* Calculate length; if it's zero, then there's nothing to do. */
839 size_t length = strlen (text);
840 if (!length)
841 {
842 if (text != cell->text)
843 free (CONST_CAST (char *, text));
844 return;
845 }
846
847 char *breaks = xmalloc (length + 1);
848 u8_possible_linebreaks (CHAR_CAST (const uint8_t *, text), length,
849 "UTF-8", breaks);
850 breaks[length] = (breaks[length - 1] == UC_BREAK_MANDATORY
851 ? UC_BREAK_PROHIBITED : UC_BREAK_POSSIBLE);
852
853 size_t pos = 0;
854 int bb_width = bb[H][1] - bb[H][0];
855 for (int y = bb[V][0]; y < bb[V][1] && pos < length; y++)
856 {
857 const uint8_t *line = CHAR_CAST (const uint8_t *, text + pos);
858 const char *b = breaks + pos;
859 size_t n = length - pos;
860
861 size_t last_break_ofs = 0;
862 int last_break_width = 0;
863 int width = 0;
864 size_t graph_ofs;
865 size_t ofs;
866
867 for (ofs = 0; ofs < n;)
868 {
869 ucs4_t uc;
870 int mblen;
871 int w;
872
873 mblen = u8_mbtouc (&uc, line + ofs, n - ofs);
874 if (b[ofs] == UC_BREAK_MANDATORY)
875 break;
876 else if (b[ofs] == UC_BREAK_POSSIBLE)
877 {
878 last_break_ofs = ofs;
879 last_break_width = width;
880 }
881
882 w = uc_width (uc, "UTF-8");
883 if (w > 0)
884 {
885 if (width + w > bb_width)
886 {
887 if (isspace (line[ofs]))
888 break;
889 else if (last_break_ofs != 0)
890 {
891 ofs = last_break_ofs;
892 width = last_break_width;
893 break;
894 }
895 }
896 width += w;
897 }
898 ofs += mblen;
899 }
900
901 /* Trim any trailing spaces off the end of the text to be drawn. */
902 for (graph_ofs = ofs; graph_ofs > 0; graph_ofs--)
903 if (!isspace (line[graph_ofs - 1]))
904 break;
905 width -= ofs - graph_ofs;
906
907 /* Draw text. */
908 text_draw (a, cell->style->cell_style.halign, cell->options,
909 cell->style->font_style.bold,
910 cell->style->font_style.underline,
911 bb, clip, y, line, graph_ofs, width);
912
913 /* If a new-line ended the line, just skip the new-line. Otherwise, skip
914 past any spaces past the end of the line (but not past a new-line). */
915 if (b[ofs] == UC_BREAK_MANDATORY)
916 ofs++;
917 else
918 while (ofs < n && isspace (line[ofs]) && b[ofs] != UC_BREAK_MANDATORY)
919 ofs++;
920
921 if (width > *widthp)
922 *widthp = width;
923 ++*heightp;
924 pos += ofs;
925 }
926
927 free (breaks);
928 if (text != cell->text)
929 free (CONST_CAST (char *, text));
930 }
931
932 void
ascii_test_write(struct output_driver * driver,const char * s,int x,int y,bool bold,bool underline)933 ascii_test_write (struct output_driver *driver,
934 const char *s, int x, int y, bool bold, bool underline)
935 {
936 struct ascii_driver *a = ascii_driver_cast (driver);
937 int bb[TABLE_N_AXES][2];
938 int width, height;
939
940 if (!a->file)
941 return;
942
943 struct area_style style = {
944 .cell_style.halign = TABLE_HALIGN_LEFT,
945 .font_style.bold = bold,
946 .font_style.underline = underline,
947 };
948 struct table_cell cell = {
949 .text = CONST_CAST (char *, s),
950 .style = &style,
951 };
952
953 bb[TABLE_HORZ][0] = x;
954 bb[TABLE_HORZ][1] = a->width;
955 bb[TABLE_VERT][0] = y;
956 bb[TABLE_VERT][1] = INT_MAX;
957
958 ascii_layout_cell (a, &cell, bb, bb, &width, &height);
959 }
960
961 void
ascii_test_set_length(struct output_driver * driver,int y,int length)962 ascii_test_set_length (struct output_driver *driver, int y, int length)
963 {
964 struct ascii_driver *a = ascii_driver_cast (driver);
965
966 if (!a->file)
967 return;
968
969 if (y < 0)
970 return;
971 u8_line_set_length (&a->lines[y], length);
972 }
973
974 void
ascii_test_flush(struct output_driver * driver)975 ascii_test_flush (struct output_driver *driver)
976 {
977 struct ascii_driver *a = ascii_driver_cast (driver);
978
979 for (size_t i = a->allocated_lines; i-- > 0;)
980 if (a->lines[i].width)
981 {
982 ascii_output_lines (a, i + 1);
983 break;
984 }
985 }
986
987 static sig_atomic_t terminal_changed = true;
988 static int terminal_width;
989
990 #if HAVE_DECL_SIGWINCH
991 static void
winch_handler(int signum UNUSED)992 winch_handler (int signum UNUSED)
993 {
994 terminal_changed = true;
995 }
996 #endif
997
998 int
get_terminal_width(void)999 get_terminal_width (void)
1000 {
1001 #if HAVE_DECL_SIGWINCH
1002 static bool setup_signal;
1003 if (!setup_signal)
1004 {
1005 setup_signal = true;
1006
1007 struct sigaction action = { .sa_handler = winch_handler };
1008 sigemptyset (&action.sa_mask);
1009 sigaction (SIGWINCH, &action, NULL);
1010 }
1011 #endif
1012
1013 if (terminal_changed)
1014 {
1015 terminal_changed = false;
1016
1017 #ifdef HAVE_TERMIOS_H
1018 struct winsize ws;
1019 if (!ioctl (0, TIOCGWINSZ, &ws))
1020 terminal_width = ws.ws_col;
1021 else
1022 #endif
1023 {
1024 if (getenv ("COLUMNS"))
1025 terminal_width = atoi (getenv ("COLUMNS"));
1026 }
1027
1028 if (terminal_width <= 0 || terminal_width > 1024)
1029 terminal_width = 79;
1030 }
1031
1032 return terminal_width;
1033 }
1034