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