table: Rename area_style to table_area_style for consistency.
[pspp] / src / output / ascii.c
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
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
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], int valign_offset,
239                              int spill[TABLE_N_AXES][2],
240                              int clip[TABLE_N_AXES][2]);
241
242 static struct ascii_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 *
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
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 *
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
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
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
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
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
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
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
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
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
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 \f
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
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
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
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
634 ascii_draw_cell (void *a_, const struct table_cell *cell, int color_idx UNUSED,
635                  int bb[TABLE_N_AXES][2], int valign_offset,
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   bb[V][0] += valign_offset;
643   ascii_layout_cell (a, cell, bb, clip, &w, &h);
644 }
645
646 static char *
647 ascii_reserve (struct ascii_driver *a, int y, int x0, int x1, int n)
648 {
649   if (y >= a->allocated_lines)
650     {
651       size_t new_alloc = MAX (25, a->allocated_lines);
652       while (new_alloc <= y)
653         new_alloc = xtimes (new_alloc, 2);
654       a->lines = xnrealloc (a->lines, new_alloc, sizeof *a->lines);
655       for (size_t i = a->allocated_lines; i < new_alloc; i++)
656         u8_line_init (&a->lines[i]);
657       a->allocated_lines = new_alloc;
658     }
659   return u8_line_reserve (&a->lines[y], x0, x1, n);
660 }
661
662 static void
663 text_draw (struct ascii_driver *a, enum table_halign halign, int options,
664            bool bold, bool underline,
665            int bb[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2],
666            int y, const uint8_t *string, int n, size_t width)
667 {
668   int x0 = MAX (0, clip[H][0]);
669   int y0 = MAX (0, clip[V][0]);
670   int x1 = MIN (a->width, clip[H][1]);
671   int y1 = clip[V][1];
672   int x;
673
674   if (y < y0 || y >= y1)
675     return;
676
677   switch (table_halign_interpret (halign, options & TAB_NUMERIC))
678     {
679     case TABLE_HALIGN_LEFT:
680       x = bb[H][0];
681       break;
682     case TABLE_HALIGN_CENTER:
683       x = (bb[H][0] + bb[H][1] - width + 1) / 2;
684       break;
685     case TABLE_HALIGN_RIGHT:
686     case TABLE_HALIGN_DECIMAL:
687       x = bb[H][1] - width;
688       break;
689     default:
690       NOT_REACHED ();
691     }
692   if (x >= x1)
693     return;
694
695   while (x < x0)
696     {
697       ucs4_t uc;
698       int mblen;
699       int w;
700
701       if (n == 0)
702         return;
703       mblen = u8_mbtouc (&uc, string, n);
704
705       string += mblen;
706       n -= mblen;
707
708       w = uc_width (uc, "UTF-8");
709       if (w > 0)
710         {
711           x += w;
712           width -= w;
713         }
714     }
715   if (n == 0)
716     return;
717
718   if (x + width > x1)
719     {
720       int ofs;
721
722       ofs = width = 0;
723       for (ofs = 0; ofs < n;)
724         {
725           ucs4_t uc;
726           int mblen;
727           int w;
728
729           mblen = u8_mbtouc (&uc, string + ofs, n - ofs);
730
731           w = uc_width (uc, "UTF-8");
732           if (w > 0)
733             {
734               if (width + w > x1 - x)
735                 break;
736               width += w;
737             }
738           ofs += mblen;
739         }
740       n = ofs;
741       if (n == 0)
742         return;
743     }
744
745   if (!a->emphasis || (!bold && !underline))
746     memcpy (ascii_reserve (a, y, x, x + width, n), string, n);
747   else
748     {
749       size_t n_out;
750       size_t ofs;
751       char *out;
752       int mblen;
753
754       /* First figure out how many bytes need to be inserted. */
755       n_out = n;
756       for (ofs = 0; ofs < n; ofs += mblen)
757         {
758           ucs4_t uc;
759           int w;
760
761           mblen = u8_mbtouc (&uc, string + ofs, n - ofs);
762           w = uc_width (uc, "UTF-8");
763
764           if (w > 0)
765             {
766               if (bold)
767                 n_out += 1 + mblen;
768               if (underline)
769                 n_out += 2;
770             }
771         }
772
773       /* Then insert them. */
774       out = ascii_reserve (a, y, x, x + width, n_out);
775       for (ofs = 0; ofs < n; ofs += mblen)
776         {
777           ucs4_t uc;
778           int w;
779
780           mblen = u8_mbtouc (&uc, string + ofs, n - ofs);
781           w = uc_width (uc, "UTF-8");
782
783           if (w > 0)
784             {
785               if (bold)
786                 {
787                   out = mempcpy (out, string + ofs, mblen);
788                   *out++ = '\b';
789                 }
790               if (underline)
791                 {
792                   *out++ = '_';
793                   *out++ = '\b';
794                 }
795             }
796           out = mempcpy (out, string + ofs, mblen);
797         }
798     }
799 }
800
801 static char *
802 add_markers (const char *text, const struct table_cell *cell)
803 {
804   struct string s = DS_EMPTY_INITIALIZER;
805   ds_put_cstr (&s, text);
806   for (size_t i = 0; i < cell->n_subscripts; i++)
807     ds_put_format (&s, "%c%s", i ? ',' : '_', cell->subscripts[i]);
808   if (cell->superscript)
809     ds_put_format (&s, "^%s", cell->superscript);
810   for (size_t i = 0; i < cell->n_footnotes; i++)
811     ds_put_format (&s, "[%s]", cell->footnotes[i]->marker);
812   return ds_steal_cstr (&s);
813 }
814
815 static void
816 ascii_layout_cell (struct ascii_driver *a, const struct table_cell *cell,
817                    int bb[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2],
818                    int *widthp, int *heightp)
819 {
820   *widthp = 0;
821   *heightp = 0;
822
823   /* Get the basic textual contents. */
824   const char *plain_text = (cell->options & TAB_MARKUP
825                             ? output_get_text_from_markup (cell->text)
826                             : cell->text);
827
828   /* Append footnotes, subscripts, superscript if any. */
829   const char *text;
830   if (cell->n_footnotes || cell->n_subscripts || cell->superscript)
831     {
832       text = add_markers (plain_text, cell);
833       if (plain_text != cell->text)
834         free (CONST_CAST (char *, plain_text));
835     }
836   else
837     text = plain_text;
838
839   /* Calculate length; if it's zero, then there's nothing to do. */
840   size_t length = strlen (text);
841   if (!length)
842     {
843       if (text != cell->text)
844         free (CONST_CAST (char *, text));
845       return;
846     }
847
848   char *breaks = xmalloc (length + 1);
849   u8_possible_linebreaks (CHAR_CAST (const uint8_t *, text), length,
850                           "UTF-8", breaks);
851   breaks[length] = (breaks[length - 1] == UC_BREAK_MANDATORY
852                     ? UC_BREAK_PROHIBITED : UC_BREAK_POSSIBLE);
853
854   size_t pos = 0;
855   int bb_width = bb[H][1] - bb[H][0];
856   for (int y = bb[V][0]; y < bb[V][1] && pos < length; y++)
857     {
858       const uint8_t *line = CHAR_CAST (const uint8_t *, text + pos);
859       const char *b = breaks + pos;
860       size_t n = length - pos;
861
862       size_t last_break_ofs = 0;
863       int last_break_width = 0;
864       int width = 0;
865       size_t graph_ofs;
866       size_t ofs;
867
868       for (ofs = 0; ofs < n;)
869         {
870           ucs4_t uc;
871           int mblen;
872           int w;
873
874           mblen = u8_mbtouc (&uc, line + ofs, n - ofs);
875           if (b[ofs] == UC_BREAK_MANDATORY)
876             break;
877           else if (b[ofs] == UC_BREAK_POSSIBLE)
878             {
879               last_break_ofs = ofs;
880               last_break_width = width;
881             }
882
883           w = uc_width (uc, "UTF-8");
884           if (w > 0)
885             {
886               if (width + w > bb_width)
887                 {
888                   if (isspace (line[ofs]))
889                     break;
890                   else if (last_break_ofs != 0)
891                     {
892                       ofs = last_break_ofs;
893                       width = last_break_width;
894                       break;
895                     }
896                 }
897               width += w;
898             }
899           ofs += mblen;
900         }
901
902       /* Trim any trailing spaces off the end of the text to be drawn. */
903       for (graph_ofs = ofs; graph_ofs > 0; graph_ofs--)
904         if (!isspace (line[graph_ofs - 1]))
905           break;
906       width -= ofs - graph_ofs;
907
908       /* Draw text. */
909       text_draw (a, cell->style->cell_style.halign, cell->options,
910                  cell->style->font_style.bold,
911                  cell->style->font_style.underline,
912                  bb, clip, y, line, graph_ofs, width);
913
914       /* If a new-line ended the line, just skip the new-line.  Otherwise, skip
915          past any spaces past the end of the line (but not past a new-line). */
916       if (b[ofs] == UC_BREAK_MANDATORY)
917         ofs++;
918       else
919         while (ofs < n && isspace (line[ofs]) && b[ofs] != UC_BREAK_MANDATORY)
920           ofs++;
921
922       if (width > *widthp)
923         *widthp = width;
924       ++*heightp;
925       pos += ofs;
926     }
927
928   free (breaks);
929   if (text != cell->text)
930     free (CONST_CAST (char *, text));
931 }
932
933 void
934 ascii_test_write (struct output_driver *driver,
935                   const char *s, int x, int y, bool bold, bool underline)
936 {
937   struct ascii_driver *a = ascii_driver_cast (driver);
938   int bb[TABLE_N_AXES][2];
939   int width, height;
940
941   if (!a->file)
942     return;
943
944   struct table_area_style style = {
945     .cell_style.halign = TABLE_HALIGN_LEFT,
946     .font_style.bold = bold,
947     .font_style.underline = underline,
948   };
949   struct table_cell cell = {
950     .text = CONST_CAST (char *, s),
951     .style = &style,
952   };
953
954   bb[TABLE_HORZ][0] = x;
955   bb[TABLE_HORZ][1] = a->width;
956   bb[TABLE_VERT][0] = y;
957   bb[TABLE_VERT][1] = INT_MAX;
958
959   ascii_layout_cell (a, &cell, bb, bb, &width, &height);
960 }
961
962 void
963 ascii_test_set_length (struct output_driver *driver, int y, int length)
964 {
965   struct ascii_driver *a = ascii_driver_cast (driver);
966
967   if (!a->file)
968     return;
969
970   if (y < 0)
971     return;
972   u8_line_set_length (&a->lines[y], length);
973 }
974
975 void
976 ascii_test_flush (struct output_driver *driver)
977 {
978   struct ascii_driver *a = ascii_driver_cast (driver);
979
980   for (size_t i = a->allocated_lines; i-- > 0;)
981     if (a->lines[i].width)
982       {
983         ascii_output_lines (a, i + 1);
984         break;
985       }
986 }
987 \f
988 static sig_atomic_t terminal_changed = true;
989 static int terminal_width;
990
991 #if HAVE_DECL_SIGWINCH
992 static void
993 winch_handler (int signum UNUSED)
994 {
995   terminal_changed = true;
996 }
997 #endif
998
999 int
1000 get_terminal_width (void)
1001 {
1002 #if HAVE_DECL_SIGWINCH
1003   static bool setup_signal;
1004   if (!setup_signal)
1005     {
1006       setup_signal = true;
1007
1008       struct sigaction action = { .sa_handler = winch_handler };
1009       sigemptyset (&action.sa_mask);
1010       sigaction (SIGWINCH, &action, NULL);
1011     }
1012 #endif
1013
1014   if (terminal_changed)
1015     {
1016       terminal_changed = false;
1017
1018 #ifdef HAVE_TERMIOS_H
1019       struct winsize ws;
1020       if (!ioctl (0, TIOCGWINSZ, &ws))
1021         terminal_width = ws.ws_col;
1022       else
1023 #endif
1024         {
1025           if (getenv ("COLUMNS"))
1026             terminal_width = atoi (getenv ("COLUMNS"));
1027         }
1028
1029       if (terminal_width <= 0 || terminal_width > 1024)
1030         terminal_width = 79;
1031     }
1032
1033   return terminal_width;
1034 }