output: Add support for Pango markup of fonts and styles.
[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/tab.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 static struct output_driver *
244 ascii_create (struct  file_handle *fh, enum settings_output_devices device_type,
245               struct string_map *o)
246 {
247   enum { BOX_ASCII, BOX_UNICODE } box;
248   int min_break[TABLE_N_AXES];
249   struct output_driver *d;
250   struct ascii_driver *a;
251
252   a = xzalloc (sizeof *a);
253   d = &a->driver;
254   output_driver_init (&a->driver, &ascii_driver_class, fh_get_file_name (fh), device_type);
255   a->append = parse_boolean (opt (d, o, "append", "false"));
256   a->emphasis = parse_boolean (opt (d, o, "emphasis", "false"));
257
258   a->chart_file_name = parse_chart_file_name (opt (d, o, "charts", fh_get_file_name (fh)));
259   a->handle = fh;
260
261   min_break[H] = parse_int (opt (d, o, "min-hbreak", "-1"), -1, INT_MAX);
262
263   a->width = parse_page_size (opt (d, o, "width", "79"));
264   a->auto_width = a->width < 0;
265   a->min_break[H] = min_break[H] >= 0 ? min_break[H] : a->width / 2;
266 #ifdef HAVE_CAIRO
267   parse_color (d, o, "background-color", "#FFFFFFFFFFFF", &a->bg);
268   parse_color (d, o, "foreground-color", "#000000000000", &a->fg);
269 #endif
270   box = parse_enum (opt (d, o, "box", "ascii"),
271                     "ascii", BOX_ASCII,
272                     "unicode", BOX_UNICODE,
273                     NULL_SENTINEL);
274   a->box = box == BOX_ASCII ? ascii_box_chars : unicode_box_chars;
275
276   a->file = NULL;
277   a->error = false;
278   a->lines = NULL;
279   a->allocated_lines = 0;
280   a->chart_cnt = 1;
281
282   a->params.draw_line = ascii_draw_line;
283   a->params.measure_cell_width = ascii_measure_cell_width;
284   a->params.measure_cell_height = ascii_measure_cell_height;
285   a->params.adjust_break = NULL;
286   a->params.draw_cell = ascii_draw_cell;
287   a->params.aux = a;
288   a->params.size[H] = a->width;
289   a->params.size[V] = INT_MAX;
290   a->params.font_size[H] = 1;
291   a->params.font_size[V] = 1;
292   for (int i = 0; i < RENDER_N_LINES; i++)
293     {
294       int width = i == RENDER_LINE_NONE ? 0 : 1;
295       a->params.line_widths[H][i] = width;
296       a->params.line_widths[V][i] = width;
297     }
298   for (int i = 0; i < TABLE_N_AXES; i++)
299     a->params.min_break[i] = a->min_break[i];
300   a->params.supports_margins = false;
301   a->params.rtl = render_direction_rtl ();
302
303   if (!update_page_size (a, true))
304     goto error;
305
306   return d;
307
308 error:
309   output_driver_destroy (d);
310   return NULL;
311 }
312
313 static int
314 parse_page_size (struct driver_option *option)
315 {
316   int dim = atol (option->default_value);
317
318   if (option->value != NULL)
319     {
320       if (!strcmp (option->value, "auto"))
321         dim = -1;
322       else
323         {
324           int value;
325           char *tail;
326
327           errno = 0;
328           value = strtol (option->value, &tail, 0);
329           if (dim >= 1 && errno != ERANGE && *tail == '\0')
330             dim = value;
331           else
332             msg (MW, _("%s: %s must be positive integer or `auto'"),
333                    option->driver_name, option->name);
334         }
335     }
336
337   driver_option_destroy (option);
338
339   return dim;
340 }
341
342 /* Re-calculates the page width based on settings, margins, and, if "auto" is
343    set, the size of the user's terminal window or GUI output window. */
344 static bool
345 update_page_size (struct ascii_driver *a, bool issue_error)
346 {
347   enum { MIN_WIDTH = 6, MIN_LENGTH = 6 };
348
349   if (a->auto_width)
350     {
351       a->params.size[H] = a->width = settings_get_viewwidth ();
352       a->params.min_break[H] = a->min_break[H] = a->width / 2;
353     }
354
355   if (a->width < MIN_WIDTH)
356     {
357       if (issue_error)
358         msg (ME,
359                _("ascii: page must be at least %d characters wide, but "
360                  "as configured is only %d characters"),
361                MIN_WIDTH,
362                a->width);
363       if (a->width < MIN_WIDTH)
364         a->params.size[H] = a->width = MIN_WIDTH;
365       return false;
366     }
367
368   return true;
369 }
370
371 static void
372 ascii_destroy (struct output_driver *driver)
373 {
374   struct ascii_driver *a = ascii_driver_cast (driver);
375   int i;
376
377   if (a->file != NULL)
378     fn_close (a->handle, a->file);
379   fh_unref (a->handle);
380   free (a->chart_file_name);
381   for (i = 0; i < a->allocated_lines; i++)
382     u8_line_destroy (&a->lines[i]);
383   free (a->lines);
384   free (a);
385 }
386
387 static void
388 ascii_flush (struct output_driver *driver)
389 {
390   struct ascii_driver *a = ascii_driver_cast (driver);
391   if (a->file)
392     fflush (a->file);
393 }
394
395 static void
396 ascii_output_lines (struct ascii_driver *a, size_t n_lines)
397 {
398   for (size_t y = 0; y < n_lines; y++)
399     {
400       struct u8_line *line = &a->lines[y];
401
402       while (ds_chomp_byte (&line->s, ' '))
403         continue;
404       fwrite (ds_data (&line->s), 1, ds_length (&line->s), a->file);
405       putc ('\n', a->file);
406
407       u8_line_clear (&a->lines[y]);
408     }
409 }
410
411 static void
412 ascii_output_table_item (struct ascii_driver *a,
413                          const struct table_item *table_item)
414 {
415   struct render_pager *p;
416
417   update_page_size (a, false);
418
419   if (a->file)
420     putc ('\n', a->file);
421   else if (!ascii_open_page (a))
422     return;
423
424   p = render_pager_create (&a->params, table_item);
425   for (int i = 0; render_pager_has_next (p); i++)
426     {
427       if (i)
428         putc ('\n', a->file);
429       ascii_output_lines (a, render_pager_draw_next (p, INT_MAX));
430     }
431   render_pager_destroy (p);
432 }
433
434 static void
435 ascii_output_text (struct ascii_driver *a, const char *text)
436 {
437   struct table_item *table_item;
438
439   table_item = table_item_create (table_from_string (TAB_LEFT, text),
440                                   NULL, NULL);
441   ascii_output_table_item (a, table_item);
442   table_item_unref (table_item);
443 }
444
445 static void
446 ascii_submit (struct output_driver *driver,
447               const struct output_item *output_item)
448 {
449   struct ascii_driver *a = ascii_driver_cast (driver);
450
451   if (a->error)
452     return;
453
454   if (is_table_item (output_item))
455     ascii_output_table_item (a, to_table_item (output_item));
456 #ifdef HAVE_CAIRO
457   else if (is_chart_item (output_item) && a->chart_file_name != NULL)
458     {
459       struct chart_item *chart_item = to_chart_item (output_item);
460       char *file_name;
461
462       file_name = xr_draw_png_chart (chart_item, a->chart_file_name,
463                                      a->chart_cnt++,
464                                      &a->fg,
465                                      &a->bg);
466       if (file_name != NULL)
467         {
468           struct text_item *text_item;
469
470           text_item = text_item_create_format (
471             TEXT_ITEM_PARAGRAPH, _("See %s for a chart."), file_name);
472
473           ascii_submit (driver, &text_item->output_item);
474           text_item_unref (text_item);
475           free (file_name);
476         }
477     }
478 #endif  /* HAVE_CAIRO */
479   else if (is_text_item (output_item))
480     {
481       const struct text_item *text_item = to_text_item (output_item);
482       enum text_item_type type = text_item_get_type (text_item);
483
484       switch (type)
485         {
486         case TEXT_ITEM_PAGE_TITLE:
487         case TEXT_ITEM_BLANK_LINE:
488           break;
489
490         case TEXT_ITEM_EJECT_PAGE:
491           break;
492
493         default:
494           ascii_output_table_item (a, text_item_to_table_item (text_item_ref (text_item)));
495           break;
496         }
497     }
498   else if (is_message_item (output_item))
499     {
500       const struct message_item *message_item = to_message_item (output_item);
501       char *s = msg_to_string (message_item_get_msg (message_item));
502       ascii_output_text (a, s);
503       free (s);
504     }
505 }
506
507 const struct output_driver_factory txt_driver_factory =
508   { "txt", "-", ascii_create };
509 const struct output_driver_factory list_driver_factory =
510   { "list", "-", ascii_create };
511
512 static const struct output_driver_class ascii_driver_class =
513   {
514     "text",
515     ascii_destroy,
516     ascii_submit,
517     ascii_flush,
518   };
519 \f
520 static char *ascii_reserve (struct ascii_driver *, int y, int x0, int x1,
521                             int n);
522 static void ascii_layout_cell (struct ascii_driver *,
523                                const struct table_cell *,
524                                int bb[TABLE_N_AXES][2],
525                                int clip[TABLE_N_AXES][2],
526                                int *width, int *height);
527
528 static void
529 ascii_draw_line (void *a_, int bb[TABLE_N_AXES][2],
530                  enum render_line_style styles[TABLE_N_AXES][2],
531                  struct cell_color colors[TABLE_N_AXES][2] UNUSED)
532 {
533   struct ascii_driver *a = a_;
534   char mbchar[6];
535   int x0, y0, x1, y1;
536   ucs4_t uc;
537   int mblen;
538   int x, y;
539
540   /* Clip to the page. */
541   x0 = MAX (bb[H][0], 0);
542   y0 = MAX (bb[V][0], 0);
543   x1 = MIN (bb[H][1], a->width);
544   y1 = bb[V][1];
545   if (x1 <= 0 || y1 <= 0 || x0 >= a->width)
546     return;
547
548   /* Draw. */
549   uc = a->box[make_box_index (styles[V][0], styles[V][1],
550                               styles[H][0], styles[H][1])];
551   mblen = u8_uctomb (CHAR_CAST (uint8_t *, mbchar), uc, 6);
552   for (y = y0; y < y1; y++)
553     {
554       char *p = ascii_reserve (a, y, x0, x1, mblen * (x1 - x0));
555       for (x = x0; x < x1; x++)
556         {
557           memcpy (p, mbchar, mblen);
558           p += mblen;
559         }
560     }
561 }
562
563 static void
564 ascii_measure_cell_width (void *a_, const struct table_cell *cell,
565                           int *min_width, int *max_width)
566 {
567   struct ascii_driver *a = a_;
568   int bb[TABLE_N_AXES][2];
569   int clip[TABLE_N_AXES][2];
570   int h;
571
572   bb[H][0] = 0;
573   bb[H][1] = INT_MAX;
574   bb[V][0] = 0;
575   bb[V][1] = INT_MAX;
576   clip[H][0] = clip[H][1] = clip[V][0] = clip[V][1] = 0;
577   ascii_layout_cell (a, cell, bb, clip, max_width, &h);
578
579   if (cell->n_contents != 1
580       || cell->contents[0].n_footnotes
581       || strchr (cell->contents[0].text, ' '))
582     {
583       bb[H][1] = 1;
584       ascii_layout_cell (a, cell, bb, clip, min_width, &h);
585     }
586   else
587     *min_width = *max_width;
588 }
589
590 static int
591 ascii_measure_cell_height (void *a_, const struct table_cell *cell, int width)
592 {
593   struct ascii_driver *a = a_;
594   int bb[TABLE_N_AXES][2];
595   int clip[TABLE_N_AXES][2];
596   int w, h;
597
598   bb[H][0] = 0;
599   bb[H][1] = width;
600   bb[V][0] = 0;
601   bb[V][1] = INT_MAX;
602   clip[H][0] = clip[H][1] = clip[V][0] = clip[V][1] = 0;
603   ascii_layout_cell (a, cell, bb, clip, &w, &h);
604   return h;
605 }
606
607 static void
608 ascii_draw_cell (void *a_, const struct table_cell *cell, int color_idx UNUSED,
609                  int bb[TABLE_N_AXES][2],
610                  int spill[TABLE_N_AXES][2] UNUSED,
611                  int clip[TABLE_N_AXES][2])
612 {
613   struct ascii_driver *a = a_;
614   int w, h;
615
616   ascii_layout_cell (a, cell, bb, clip, &w, &h);
617 }
618
619 static char *
620 ascii_reserve (struct ascii_driver *a, int y, int x0, int x1, int n)
621 {
622   if (y >= a->allocated_lines)
623     {
624       size_t new_alloc = MAX (25, a->allocated_lines);
625       while (new_alloc <= y)
626         new_alloc = xtimes (new_alloc, 2);
627       a->lines = xnrealloc (a->lines, new_alloc, sizeof *a->lines);
628       for (size_t i = a->allocated_lines; i < new_alloc; i++)
629         u8_line_init (&a->lines[i]);
630       a->allocated_lines = new_alloc;
631     }
632   return u8_line_reserve (&a->lines[y], x0, x1, n);
633 }
634
635 static void
636 text_draw (struct ascii_driver *a, unsigned int options,
637            bool bold, bool underline,
638            int bb[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2],
639            int y, const uint8_t *string, int n, size_t width)
640 {
641   int x0 = MAX (0, clip[H][0]);
642   int y0 = MAX (0, clip[V][0]);
643   int x1 = MIN (a->width, clip[H][1]);
644   int y1 = clip[V][1];
645   int x;
646
647   if (y < y0 || y >= y1)
648     return;
649
650   switch (options & TAB_HALIGN)
651     {
652     case TAB_LEFT:
653       x = bb[H][0];
654       break;
655     case TAB_CENTER:
656       x = (bb[H][0] + bb[H][1] - width + 1) / 2;
657       break;
658     case TAB_RIGHT:
659       x = bb[H][1] - width;
660       break;
661     default:
662       NOT_REACHED ();
663     }
664   if (x >= x1)
665     return;
666
667   while (x < x0)
668     {
669       ucs4_t uc;
670       int mblen;
671       int w;
672
673       if (n == 0)
674         return;
675       mblen = u8_mbtouc (&uc, string, n);
676
677       string += mblen;
678       n -= mblen;
679
680       w = uc_width (uc, "UTF-8");
681       if (w > 0)
682         {
683           x += w;
684           width -= w;
685         }
686     }
687   if (n == 0)
688     return;
689
690   if (x + width > x1)
691     {
692       int ofs;
693
694       ofs = width = 0;
695       for (ofs = 0; ofs < n; )
696         {
697           ucs4_t uc;
698           int mblen;
699           int w;
700
701           mblen = u8_mbtouc (&uc, string + ofs, n - ofs);
702
703           w = uc_width (uc, "UTF-8");
704           if (w > 0)
705             {
706               if (width + w > x1 - x)
707                 break;
708               width += w;
709             }
710           ofs += mblen;
711         }
712       n = ofs;
713       if (n == 0)
714         return;
715     }
716
717   if (!a->emphasis || (!bold && !underline))
718     memcpy (ascii_reserve (a, y, x, x + width, n), string, n);
719   else
720     {
721       size_t n_out;
722       size_t ofs;
723       char *out;
724       int mblen;
725
726       /* First figure out how many bytes need to be inserted. */
727       n_out = n;
728       for (ofs = 0; ofs < n; ofs += mblen)
729         {
730           ucs4_t uc;
731           int w;
732
733           mblen = u8_mbtouc (&uc, string + ofs, n - ofs);
734           w = uc_width (uc, "UTF-8");
735
736           if (w > 0)
737             {
738               if (bold)
739                 n_out += 1 + mblen;
740               if (underline)
741                 n_out += 2;
742             }
743         }
744
745       /* Then insert them. */
746       out = ascii_reserve (a, y, x, x + width, n_out);
747       for (ofs = 0; ofs < n; ofs += mblen)
748         {
749           ucs4_t uc;
750           int w;
751
752           mblen = u8_mbtouc (&uc, string + ofs, n - ofs);
753           w = uc_width (uc, "UTF-8");
754
755           if (w > 0)
756             {
757               if (bold)
758                 {
759                   out = mempcpy (out, string + ofs, mblen);
760                   *out++ = '\b';
761                 }
762               if (underline)
763                 {
764                   *out++ = '_';
765                   *out++ = '\b';
766                 }
767             }
768           out = mempcpy (out, string + ofs, mblen);
769         }
770     }
771 }
772
773 static char *
774 add_footnote_markers (const char *text, const struct cell_contents *contents)
775 {
776   struct string s = DS_EMPTY_INITIALIZER;
777   ds_put_cstr (&s, text);
778   for (size_t i = 0; i < contents->n_footnotes; i++)
779     ds_put_format (&s, "[%s]", contents->footnotes[i]->marker);
780   return ds_steal_cstr (&s);
781 }
782
783 static int
784 ascii_layout_cell_text (struct ascii_driver *a,
785                         const struct cell_contents *contents,
786                         bool bold, bool underline,
787                         int bb[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2],
788                         int *widthp)
789 {
790   char *breaks;
791   int bb_width;
792   size_t pos;
793
794   int y = bb[V][0];
795
796   /* Get the basic textual contents. */
797   const char *plain_text = (contents->options & TAB_MARKUP
798                             ? output_get_text_from_markup (contents->text)
799                             : contents->text);
800
801   /* Append footnote markers if any. */
802   const char *text;
803   if (contents->n_footnotes)
804     {
805       text = add_footnote_markers (plain_text, contents);
806       if (plain_text != contents->text)
807         free (CONST_CAST (char *, plain_text));
808     }
809   else
810     text = plain_text;
811
812   /* Calculate length; if it's zero, then there's nothing to do. */
813   size_t length = strlen (text);
814   if (!length)
815     {
816       if (text != contents->text)
817         free (CONST_CAST (char *, text));
818       return y;
819     }
820
821   breaks = xmalloc (length + 1);
822   u8_possible_linebreaks (CHAR_CAST (const uint8_t *, text), length,
823                           "UTF-8", breaks);
824   breaks[length] = (breaks[length - 1] == UC_BREAK_MANDATORY
825                     ? UC_BREAK_PROHIBITED : UC_BREAK_POSSIBLE);
826
827   pos = 0;
828   bb_width = bb[H][1] - bb[H][0];
829   for (y = bb[V][0]; y < bb[V][1] && pos < length; y++)
830     {
831       const uint8_t *line = CHAR_CAST (const uint8_t *, text + pos);
832       const char *b = breaks + pos;
833       size_t n = length - pos;
834
835       size_t last_break_ofs = 0;
836       int last_break_width = 0;
837       int width = 0;
838       size_t graph_ofs;
839       size_t ofs;
840
841       for (ofs = 0; ofs < n; )
842         {
843           ucs4_t uc;
844           int mblen;
845           int w;
846
847           mblen = u8_mbtouc (&uc, line + ofs, n - ofs);
848           if (b[ofs] == UC_BREAK_MANDATORY)
849             break;
850           else if (b[ofs] == UC_BREAK_POSSIBLE)
851             {
852               last_break_ofs = ofs;
853               last_break_width = width;
854             }
855
856           w = uc_width (uc, "UTF-8");
857           if (w > 0)
858             {
859               if (width + w > bb_width)
860                 {
861                   if (isspace (line[ofs]))
862                     break;
863                   else if (last_break_ofs != 0)
864                     {
865                       ofs = last_break_ofs;
866                       width = last_break_width;
867                       break;
868                     }
869                 }
870               width += w;
871             }
872           ofs += mblen;
873         }
874
875       /* Trim any trailing spaces off the end of the text to be drawn. */
876       for (graph_ofs = ofs; graph_ofs > 0; graph_ofs--)
877         if (!isspace (line[graph_ofs - 1]))
878           break;
879       width -= ofs - graph_ofs;
880
881       /* Draw text. */
882       text_draw (a, contents->options, bold, underline,
883                  bb, clip, y, line, graph_ofs, width);
884
885       /* If a new-line ended the line, just skip the new-line.  Otherwise, skip
886          past any spaces past the end of the line (but not past a new-line). */
887       if (b[ofs] == UC_BREAK_MANDATORY)
888         ofs++;
889       else
890         while (ofs < n && isspace (line[ofs]) && b[ofs] != UC_BREAK_MANDATORY)
891           ofs++;
892
893       if (width > *widthp)
894         *widthp = width;
895       pos += ofs;
896     }
897
898   free (breaks);
899   if (text != contents->text)
900     free (CONST_CAST (char *, text));
901
902   return y;
903 }
904
905 static void
906 ascii_layout_cell (struct ascii_driver *a, const struct table_cell *cell,
907                    int bb_[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2],
908                    int *widthp, int *heightp)
909 {
910   int bb[TABLE_N_AXES][2];
911   size_t i;
912
913   *widthp = 0;
914   *heightp = 0;
915
916   memcpy (bb, bb_, sizeof bb);
917   for (i = 0; i < cell->n_contents && bb[V][0] < bb[V][1]; i++)
918     {
919       const struct cell_contents *contents = &cell->contents[i];
920
921       /* Put a blank line between contents. */
922       if (i > 0)
923         {
924           bb[V][0]++;
925           if (bb[V][0] >= bb[V][1])
926             break;
927         }
928
929       bb[V][0] = ascii_layout_cell_text (a, contents, cell->style->bold,
930                                          cell->style->underline,
931                                          bb, clip, widthp);
932     }
933   *heightp = bb[V][0] - bb_[V][0];
934 }
935
936 void
937 ascii_test_write (struct output_driver *driver,
938                   const char *s, int x, int y, bool bold, bool underline)
939 {
940   struct ascii_driver *a = ascii_driver_cast (driver);
941   int bb[TABLE_N_AXES][2];
942   int width, height;
943
944   if (a->file == NULL && !ascii_open_page (a))
945     return;
946
947   struct cell_contents contents = {
948     .options = TAB_LEFT,
949     .text = CONST_CAST (char *, s),
950   };
951   struct cell_style cell_style = {
952     .bold = bold,
953     .underline = underline,
954   };
955   struct table_cell cell = {
956     .contents = &contents,
957     .n_contents = 1,
958     .style = &cell_style,
959   };
960
961   bb[TABLE_HORZ][0] = x;
962   bb[TABLE_HORZ][1] = a->width;
963   bb[TABLE_VERT][0] = y;
964   bb[TABLE_VERT][1] = INT_MAX;
965
966   ascii_layout_cell (a, &cell, bb, bb, &width, &height);
967 }
968
969 void
970 ascii_test_set_length (struct output_driver *driver, int y, int length)
971 {
972   struct ascii_driver *a = ascii_driver_cast (driver);
973
974   if (a->file == NULL && !ascii_open_page (a))
975     return;
976
977   if (y < 0)
978     return;
979   u8_line_set_length (&a->lines[y], length);
980 }
981
982 void
983 ascii_test_flush (struct output_driver *driver)
984 {
985   struct ascii_driver *a = ascii_driver_cast (driver);
986
987   for (size_t i = a->allocated_lines; i-- > 0; )
988     if (a->lines[i].width)
989       {
990         ascii_output_lines (a, i + 1);
991         break;
992       }
993 }
994 \f
995 /* ascii_close_page () and support routines. */
996
997 #if HAVE_DECL_SIGWINCH
998 static struct ascii_driver *the_driver;
999
1000 static void
1001 winch_handler (int signum UNUSED)
1002 {
1003   update_page_size (the_driver, false);
1004 }
1005 #endif
1006
1007 static bool
1008 ascii_open_page (struct ascii_driver *a)
1009 {
1010   if (a->error)
1011     return false;
1012
1013   if (a->file == NULL)
1014     {
1015       a->file = fn_open (a->handle, a->append ? "a" : "w");
1016       if (a->file != NULL)
1017         {
1018           if ( isatty (fileno (a->file)))
1019             {
1020 #if HAVE_DECL_SIGWINCH
1021               struct sigaction action;
1022               sigemptyset (&action.sa_mask);
1023               action.sa_flags = 0;
1024               action.sa_handler = winch_handler;
1025               the_driver = a;
1026               sigaction (SIGWINCH, &action, NULL);
1027 #endif
1028               a->auto_width = true;
1029             }
1030         }
1031       else
1032         {
1033           msg_error (errno, _("ascii: opening output file `%s'"),
1034                      fh_get_file_name (a->handle));
1035           a->error = true;
1036           return false;
1037         }
1038     }
1039
1040   return true;
1041 }