table: Make table cells have exactly one piece of content again.
[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_footnotes || strchr (cell->text, ' '))
580     {
581       bb[H][1] = 1;
582       ascii_layout_cell (a, cell, bb, clip, min_width, &h);
583     }
584   else
585     *min_width = *max_width;
586 }
587
588 static int
589 ascii_measure_cell_height (void *a_, const struct table_cell *cell, int width)
590 {
591   struct ascii_driver *a = a_;
592   int bb[TABLE_N_AXES][2];
593   int clip[TABLE_N_AXES][2];
594   int w, h;
595
596   bb[H][0] = 0;
597   bb[H][1] = width;
598   bb[V][0] = 0;
599   bb[V][1] = INT_MAX;
600   clip[H][0] = clip[H][1] = clip[V][0] = clip[V][1] = 0;
601   ascii_layout_cell (a, cell, bb, clip, &w, &h);
602   return h;
603 }
604
605 static void
606 ascii_draw_cell (void *a_, const struct table_cell *cell, int color_idx UNUSED,
607                  int bb[TABLE_N_AXES][2],
608                  int spill[TABLE_N_AXES][2] UNUSED,
609                  int clip[TABLE_N_AXES][2])
610 {
611   struct ascii_driver *a = a_;
612   int w, h;
613
614   ascii_layout_cell (a, cell, bb, clip, &w, &h);
615 }
616
617 static char *
618 ascii_reserve (struct ascii_driver *a, int y, int x0, int x1, int n)
619 {
620   if (y >= a->allocated_lines)
621     {
622       size_t new_alloc = MAX (25, a->allocated_lines);
623       while (new_alloc <= y)
624         new_alloc = xtimes (new_alloc, 2);
625       a->lines = xnrealloc (a->lines, new_alloc, sizeof *a->lines);
626       for (size_t i = a->allocated_lines; i < new_alloc; i++)
627         u8_line_init (&a->lines[i]);
628       a->allocated_lines = new_alloc;
629     }
630   return u8_line_reserve (&a->lines[y], x0, x1, n);
631 }
632
633 static void
634 text_draw (struct ascii_driver *a, unsigned int options,
635            bool bold, bool underline,
636            int bb[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2],
637            int y, const uint8_t *string, int n, size_t width)
638 {
639   int x0 = MAX (0, clip[H][0]);
640   int y0 = MAX (0, clip[V][0]);
641   int x1 = MIN (a->width, clip[H][1]);
642   int y1 = clip[V][1];
643   int x;
644
645   if (y < y0 || y >= y1)
646     return;
647
648   switch (options & TAB_HALIGN)
649     {
650     case TAB_LEFT:
651       x = bb[H][0];
652       break;
653     case TAB_CENTER:
654       x = (bb[H][0] + bb[H][1] - width + 1) / 2;
655       break;
656     case TAB_RIGHT:
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->options, cell->style->bold, cell->style->underline,
876                  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       ++*heightp;
889       pos += ofs;
890     }
891
892   free (breaks);
893   if (text != cell->text)
894     free (CONST_CAST (char *, text));
895 }
896
897 void
898 ascii_test_write (struct output_driver *driver,
899                   const char *s, int x, int y, bool bold, bool underline)
900 {
901   struct ascii_driver *a = ascii_driver_cast (driver);
902   int bb[TABLE_N_AXES][2];
903   int width, height;
904
905   if (a->file == NULL && !ascii_open_page (a))
906     return;
907
908   struct cell_style style = {
909     .bold = bold,
910     .underline = underline,
911   };
912   struct table_cell cell = {
913     .options = TAB_LEFT,
914     .text = CONST_CAST (char *, s),
915     .style = &style,
916   };
917
918   bb[TABLE_HORZ][0] = x;
919   bb[TABLE_HORZ][1] = a->width;
920   bb[TABLE_VERT][0] = y;
921   bb[TABLE_VERT][1] = INT_MAX;
922
923   ascii_layout_cell (a, &cell, bb, bb, &width, &height);
924 }
925
926 void
927 ascii_test_set_length (struct output_driver *driver, int y, int length)
928 {
929   struct ascii_driver *a = ascii_driver_cast (driver);
930
931   if (a->file == NULL && !ascii_open_page (a))
932     return;
933
934   if (y < 0)
935     return;
936   u8_line_set_length (&a->lines[y], length);
937 }
938
939 void
940 ascii_test_flush (struct output_driver *driver)
941 {
942   struct ascii_driver *a = ascii_driver_cast (driver);
943
944   for (size_t i = a->allocated_lines; i-- > 0; )
945     if (a->lines[i].width)
946       {
947         ascii_output_lines (a, i + 1);
948         break;
949       }
950 }
951 \f
952 /* ascii_close_page () and support routines. */
953
954 #if HAVE_DECL_SIGWINCH
955 static struct ascii_driver *the_driver;
956
957 static void
958 winch_handler (int signum UNUSED)
959 {
960   update_page_size (the_driver, false);
961 }
962 #endif
963
964 static bool
965 ascii_open_page (struct ascii_driver *a)
966 {
967   if (a->error)
968     return false;
969
970   if (a->file == NULL)
971     {
972       a->file = fn_open (a->handle, a->append ? "a" : "w");
973       if (a->file != NULL)
974         {
975           if ( isatty (fileno (a->file)))
976             {
977 #if HAVE_DECL_SIGWINCH
978               struct sigaction action;
979               sigemptyset (&action.sa_mask);
980               action.sa_flags = 0;
981               action.sa_handler = winch_handler;
982               the_driver = a;
983               sigaction (SIGWINCH, &action, NULL);
984 #endif
985               a->auto_width = true;
986             }
987         }
988       else
989         {
990           msg_error (errno, _("ascii: opening output file `%s'"),
991                      fh_get_file_name (a->handle));
992           a->error = true;
993           return false;
994         }
995     }
996
997   return true;
998 }