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