output: Reimplement table_from_string in terms of tab.
[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 (text), NULL, NULL));
465 }
466
467 static void
468 ascii_submit (struct output_driver *driver,
469               const struct output_item *output_item)
470 {
471   struct ascii_driver *a = ascii_driver_cast (driver);
472
473   if (a->error)
474     return;
475
476   if (is_table_item (output_item))
477     ascii_output_table_item (a, to_table_item (output_item));
478 #ifdef HAVE_CAIRO
479   else if (is_chart_item (output_item) && a->chart_file_name != NULL)
480     {
481       struct chart_item *chart_item = to_chart_item (output_item);
482       char *file_name;
483
484       file_name = xr_draw_png_chart (chart_item, a->chart_file_name,
485                                      a->chart_cnt++,
486                                      &a->fg,
487                                      &a->bg);
488       if (file_name != NULL)
489         {
490           struct text_item *text_item;
491
492           text_item = text_item_create_format (
493             TEXT_ITEM_PARAGRAPH, _("See %s for a chart."), file_name);
494
495           ascii_submit (driver, &text_item->output_item);
496           text_item_unref (text_item);
497           free (file_name);
498         }
499     }
500 #endif  /* HAVE_CAIRO */
501   else if (is_text_item (output_item))
502     {
503       const struct text_item *text_item = to_text_item (output_item);
504       enum text_item_type type = text_item_get_type (text_item);
505
506       switch (type)
507         {
508         case TEXT_ITEM_PAGE_TITLE:
509         case TEXT_ITEM_BLANK_LINE:
510           break;
511
512         case TEXT_ITEM_EJECT_PAGE:
513           break;
514
515         default:
516           ascii_output_table_item_unref (
517             a, text_item_to_table_item (text_item_ref (text_item)));
518           break;
519         }
520     }
521   else if (is_message_item (output_item))
522     {
523       const struct message_item *message_item = to_message_item (output_item);
524       char *s = msg_to_string (message_item_get_msg (message_item));
525       ascii_output_text (a, s);
526       free (s);
527     }
528 }
529
530 const struct output_driver_factory txt_driver_factory =
531   { "txt", "-", ascii_create };
532 const struct output_driver_factory list_driver_factory =
533   { "list", "-", ascii_create };
534
535 static const struct output_driver_class ascii_driver_class =
536   {
537     "text",
538     ascii_destroy,
539     ascii_submit,
540     ascii_flush,
541   };
542 \f
543 static char *ascii_reserve (struct ascii_driver *, int y, int x0, int x1,
544                             int n);
545 static void ascii_layout_cell (struct ascii_driver *,
546                                const struct table_cell *,
547                                int bb[TABLE_N_AXES][2],
548                                int clip[TABLE_N_AXES][2],
549                                int *width, int *height);
550
551 static void
552 ascii_draw_line (void *a_, int bb[TABLE_N_AXES][2],
553                  enum render_line_style styles[TABLE_N_AXES][2],
554                  struct cell_color colors[TABLE_N_AXES][2] UNUSED)
555 {
556   struct ascii_driver *a = a_;
557   char mbchar[6];
558   int x0, y0, x1, y1;
559   ucs4_t uc;
560   int mblen;
561   int x, y;
562
563   /* Clip to the page. */
564   x0 = MAX (bb[H][0], 0);
565   y0 = MAX (bb[V][0], 0);
566   x1 = MIN (bb[H][1], a->width);
567   y1 = bb[V][1];
568   if (x1 <= 0 || y1 <= 0 || x0 >= a->width)
569     return;
570
571   /* Draw. */
572   uc = a->box[make_box_index (styles[V][0], styles[V][1],
573                               styles[H][0], styles[H][1])];
574   mblen = u8_uctomb (CHAR_CAST (uint8_t *, mbchar), uc, 6);
575   for (y = y0; y < y1; y++)
576     {
577       char *p = ascii_reserve (a, y, x0, x1, mblen * (x1 - x0));
578       for (x = x0; x < x1; x++)
579         {
580           memcpy (p, mbchar, mblen);
581           p += mblen;
582         }
583     }
584 }
585
586 static void
587 ascii_measure_cell_width (void *a_, const struct table_cell *cell,
588                           int *min_width, int *max_width)
589 {
590   struct ascii_driver *a = a_;
591   int bb[TABLE_N_AXES][2];
592   int clip[TABLE_N_AXES][2];
593   int h;
594
595   bb[H][0] = 0;
596   bb[H][1] = INT_MAX;
597   bb[V][0] = 0;
598   bb[V][1] = INT_MAX;
599   clip[H][0] = clip[H][1] = clip[V][0] = clip[V][1] = 0;
600   ascii_layout_cell (a, cell, bb, clip, max_width, &h);
601
602   if (cell->n_footnotes || strchr (cell->text, ' '))
603     {
604       bb[H][1] = 1;
605       ascii_layout_cell (a, cell, bb, clip, min_width, &h);
606     }
607   else
608     *min_width = *max_width;
609 }
610
611 static int
612 ascii_measure_cell_height (void *a_, const struct table_cell *cell, int width)
613 {
614   struct ascii_driver *a = a_;
615   int bb[TABLE_N_AXES][2];
616   int clip[TABLE_N_AXES][2];
617   int w, h;
618
619   bb[H][0] = 0;
620   bb[H][1] = width;
621   bb[V][0] = 0;
622   bb[V][1] = INT_MAX;
623   clip[H][0] = clip[H][1] = clip[V][0] = clip[V][1] = 0;
624   ascii_layout_cell (a, cell, bb, clip, &w, &h);
625   return h;
626 }
627
628 static void
629 ascii_draw_cell (void *a_, const struct table_cell *cell, int color_idx UNUSED,
630                  int bb[TABLE_N_AXES][2],
631                  int spill[TABLE_N_AXES][2] UNUSED,
632                  int clip[TABLE_N_AXES][2])
633 {
634   struct ascii_driver *a = a_;
635   int w, h;
636
637   ascii_layout_cell (a, cell, bb, clip, &w, &h);
638 }
639
640 static char *
641 ascii_reserve (struct ascii_driver *a, int y, int x0, int x1, int n)
642 {
643   if (y >= a->allocated_lines)
644     {
645       size_t new_alloc = MAX (25, a->allocated_lines);
646       while (new_alloc <= y)
647         new_alloc = xtimes (new_alloc, 2);
648       a->lines = xnrealloc (a->lines, new_alloc, sizeof *a->lines);
649       for (size_t i = a->allocated_lines; i < new_alloc; i++)
650         u8_line_init (&a->lines[i]);
651       a->allocated_lines = new_alloc;
652     }
653   return u8_line_reserve (&a->lines[y], x0, x1, n);
654 }
655
656 static void
657 text_draw (struct ascii_driver *a, enum table_halign halign, int options,
658            bool bold, bool underline,
659            int bb[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2],
660            int y, const uint8_t *string, int n, size_t width)
661 {
662   int x0 = MAX (0, clip[H][0]);
663   int y0 = MAX (0, clip[V][0]);
664   int x1 = MIN (a->width, clip[H][1]);
665   int y1 = clip[V][1];
666   int x;
667
668   if (y < y0 || y >= y1)
669     return;
670
671   switch (table_halign_interpret (halign, options & TAB_NUMERIC))
672     {
673     case TABLE_HALIGN_LEFT:
674       x = bb[H][0];
675       break;
676     case TABLE_HALIGN_CENTER:
677       x = (bb[H][0] + bb[H][1] - width + 1) / 2;
678       break;
679     case TABLE_HALIGN_RIGHT:
680     case TABLE_HALIGN_DECIMAL:
681       x = bb[H][1] - width;
682       break;
683     default:
684       NOT_REACHED ();
685     }
686   if (x >= x1)
687     return;
688
689   while (x < x0)
690     {
691       ucs4_t uc;
692       int mblen;
693       int w;
694
695       if (n == 0)
696         return;
697       mblen = u8_mbtouc (&uc, string, n);
698
699       string += mblen;
700       n -= mblen;
701
702       w = uc_width (uc, "UTF-8");
703       if (w > 0)
704         {
705           x += w;
706           width -= w;
707         }
708     }
709   if (n == 0)
710     return;
711
712   if (x + width > x1)
713     {
714       int ofs;
715
716       ofs = width = 0;
717       for (ofs = 0; ofs < n; )
718         {
719           ucs4_t uc;
720           int mblen;
721           int w;
722
723           mblen = u8_mbtouc (&uc, string + ofs, n - ofs);
724
725           w = uc_width (uc, "UTF-8");
726           if (w > 0)
727             {
728               if (width + w > x1 - x)
729                 break;
730               width += w;
731             }
732           ofs += mblen;
733         }
734       n = ofs;
735       if (n == 0)
736         return;
737     }
738
739   if (!a->emphasis || (!bold && !underline))
740     memcpy (ascii_reserve (a, y, x, x + width, n), string, n);
741   else
742     {
743       size_t n_out;
744       size_t ofs;
745       char *out;
746       int mblen;
747
748       /* First figure out how many bytes need to be inserted. */
749       n_out = n;
750       for (ofs = 0; ofs < n; ofs += mblen)
751         {
752           ucs4_t uc;
753           int w;
754
755           mblen = u8_mbtouc (&uc, string + ofs, n - ofs);
756           w = uc_width (uc, "UTF-8");
757
758           if (w > 0)
759             {
760               if (bold)
761                 n_out += 1 + mblen;
762               if (underline)
763                 n_out += 2;
764             }
765         }
766
767       /* Then insert them. */
768       out = ascii_reserve (a, y, x, x + width, n_out);
769       for (ofs = 0; ofs < n; ofs += mblen)
770         {
771           ucs4_t uc;
772           int w;
773
774           mblen = u8_mbtouc (&uc, string + ofs, n - ofs);
775           w = uc_width (uc, "UTF-8");
776
777           if (w > 0)
778             {
779               if (bold)
780                 {
781                   out = mempcpy (out, string + ofs, mblen);
782                   *out++ = '\b';
783                 }
784               if (underline)
785                 {
786                   *out++ = '_';
787                   *out++ = '\b';
788                 }
789             }
790           out = mempcpy (out, string + ofs, mblen);
791         }
792     }
793 }
794
795 static char *
796 add_footnote_markers (const char *text, const struct table_cell *cell)
797 {
798   struct string s = DS_EMPTY_INITIALIZER;
799   ds_put_cstr (&s, text);
800   for (size_t i = 0; i < cell->n_footnotes; i++)
801     ds_put_format (&s, "[%s]", cell->footnotes[i]->marker);
802   return ds_steal_cstr (&s);
803 }
804
805 static void
806 ascii_layout_cell (struct ascii_driver *a, const struct table_cell *cell,
807                    int bb[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2],
808                    int *widthp, int *heightp)
809 {
810   *widthp = 0;
811   *heightp = 0;
812
813   /* Get the basic textual contents. */
814   const char *plain_text = (cell->options & TAB_MARKUP
815                             ? output_get_text_from_markup (cell->text)
816                             : cell->text);
817
818   /* Append footnote markers if any. */
819   const char *text;
820   if (cell->n_footnotes)
821     {
822       text = add_footnote_markers (plain_text, cell);
823       if (plain_text != cell->text)
824         free (CONST_CAST (char *, plain_text));
825     }
826   else
827     text = plain_text;
828
829   /* Calculate length; if it's zero, then there's nothing to do. */
830   size_t length = strlen (text);
831   if (!length)
832     {
833       if (text != cell->text)
834         free (CONST_CAST (char *, text));
835       return;
836     }
837
838   char *breaks = xmalloc (length + 1);
839   u8_possible_linebreaks (CHAR_CAST (const uint8_t *, text), length,
840                           "UTF-8", breaks);
841   breaks[length] = (breaks[length - 1] == UC_BREAK_MANDATORY
842                     ? UC_BREAK_PROHIBITED : UC_BREAK_POSSIBLE);
843
844   size_t pos = 0;
845   int bb_width = bb[H][1] - bb[H][0];
846   for (int y = bb[V][0]; y < bb[V][1] && pos < length; y++)
847     {
848       const uint8_t *line = CHAR_CAST (const uint8_t *, text + pos);
849       const char *b = breaks + pos;
850       size_t n = length - pos;
851
852       size_t last_break_ofs = 0;
853       int last_break_width = 0;
854       int width = 0;
855       size_t graph_ofs;
856       size_t ofs;
857
858       for (ofs = 0; ofs < n; )
859         {
860           ucs4_t uc;
861           int mblen;
862           int w;
863
864           mblen = u8_mbtouc (&uc, line + ofs, n - ofs);
865           if (b[ofs] == UC_BREAK_MANDATORY)
866             break;
867           else if (b[ofs] == UC_BREAK_POSSIBLE)
868             {
869               last_break_ofs = ofs;
870               last_break_width = width;
871             }
872
873           w = uc_width (uc, "UTF-8");
874           if (w > 0)
875             {
876               if (width + w > bb_width)
877                 {
878                   if (isspace (line[ofs]))
879                     break;
880                   else if (last_break_ofs != 0)
881                     {
882                       ofs = last_break_ofs;
883                       width = last_break_width;
884                       break;
885                     }
886                 }
887               width += w;
888             }
889           ofs += mblen;
890         }
891
892       /* Trim any trailing spaces off the end of the text to be drawn. */
893       for (graph_ofs = ofs; graph_ofs > 0; graph_ofs--)
894         if (!isspace (line[graph_ofs - 1]))
895           break;
896       width -= ofs - graph_ofs;
897
898       /* Draw text. */
899       text_draw (a, cell->style->cell_style.halign, cell->options,
900                  cell->style->font_style.bold,
901                  cell->style->font_style.underline,
902                  bb, clip, y, line, graph_ofs, width);
903
904       /* If a new-line ended the line, just skip the new-line.  Otherwise, skip
905          past any spaces past the end of the line (but not past a new-line). */
906       if (b[ofs] == UC_BREAK_MANDATORY)
907         ofs++;
908       else
909         while (ofs < n && isspace (line[ofs]) && b[ofs] != UC_BREAK_MANDATORY)
910           ofs++;
911
912       if (width > *widthp)
913         *widthp = width;
914       ++*heightp;
915       pos += ofs;
916     }
917
918   free (breaks);
919   if (text != cell->text)
920     free (CONST_CAST (char *, text));
921 }
922
923 void
924 ascii_test_write (struct output_driver *driver,
925                   const char *s, int x, int y, bool bold, bool underline)
926 {
927   struct ascii_driver *a = ascii_driver_cast (driver);
928   int bb[TABLE_N_AXES][2];
929   int width, height;
930
931   if (a->file == NULL && !ascii_open_page (a))
932     return;
933
934   struct area_style style = {
935     .cell_style.halign = TABLE_HALIGN_LEFT,
936     .font_style.bold = bold,
937     .font_style.underline = underline,
938   };
939   struct table_cell cell = {
940     .text = CONST_CAST (char *, s),
941     .style = &style,
942   };
943
944   bb[TABLE_HORZ][0] = x;
945   bb[TABLE_HORZ][1] = a->width;
946   bb[TABLE_VERT][0] = y;
947   bb[TABLE_VERT][1] = INT_MAX;
948
949   ascii_layout_cell (a, &cell, bb, bb, &width, &height);
950 }
951
952 void
953 ascii_test_set_length (struct output_driver *driver, int y, int length)
954 {
955   struct ascii_driver *a = ascii_driver_cast (driver);
956
957   if (a->file == NULL && !ascii_open_page (a))
958     return;
959
960   if (y < 0)
961     return;
962   u8_line_set_length (&a->lines[y], length);
963 }
964
965 void
966 ascii_test_flush (struct output_driver *driver)
967 {
968   struct ascii_driver *a = ascii_driver_cast (driver);
969
970   for (size_t i = a->allocated_lines; i-- > 0; )
971     if (a->lines[i].width)
972       {
973         ascii_output_lines (a, i + 1);
974         break;
975       }
976 }
977 \f
978 /* ascii_close_page () and support routines. */
979
980 #if HAVE_DECL_SIGWINCH
981 static struct ascii_driver *the_driver;
982
983 static void
984 winch_handler (int signum UNUSED)
985 {
986   update_page_size (the_driver, false);
987 }
988 #endif
989
990 static bool
991 ascii_open_page (struct ascii_driver *a)
992 {
993   if (a->error)
994     return false;
995
996   if (a->file == NULL)
997     {
998       a->file = fn_open (a->handle, a->append ? "a" : "w");
999       if (a->file != NULL)
1000         {
1001           if ( isatty (fileno (a->file)))
1002             {
1003 #if HAVE_DECL_SIGWINCH
1004               struct sigaction action;
1005               sigemptyset (&action.sa_mask);
1006               action.sa_flags = 0;
1007               action.sa_handler = winch_handler;
1008               the_driver = a;
1009               sigaction (SIGWINCH, &action, NULL);
1010 #endif
1011               a->auto_width = true;
1012             }
1013         }
1014       else
1015         {
1016           msg_error (errno, _("ascii: opening output file `%s'"),
1017                      fh_get_file_name (a->handle));
1018           a->error = true;
1019           return false;
1020         }
1021     }
1022
1023   return true;
1024 }