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