Whitespace changes only.
[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],
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],
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 *
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
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 *
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
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
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
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
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 \f
987 static sig_atomic_t terminal_changed = true;
988 static int terminal_width;
989
990 #if HAVE_DECL_SIGWINCH
991 static void
992 winch_handler (int signum UNUSED)
993 {
994   terminal_changed = true;
995 }
996 #endif
997
998 int
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 }