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