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