72c628871c2317950cb3a9ac0a21bad69afd04d3
[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   };
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 static void ascii_measure_cell_width (void *, const struct table_cell *,
219                                       int *min, int *max);
220 static int ascii_measure_cell_height (void *, const struct table_cell *,
221                                       int width);
222 static void ascii_draw_cell (void *, const struct table_cell *,
223                              int bb[TABLE_N_AXES][2],
224                              int spill[TABLE_N_AXES][2],
225                              int clip[TABLE_N_AXES][2]);
226
227 static struct ascii_driver *
228 ascii_driver_cast (struct output_driver *driver)
229 {
230   assert (driver->class == &ascii_driver_class);
231   return UP_CAST (driver, struct ascii_driver, driver);
232 }
233
234 static struct driver_option *
235 opt (struct output_driver *d, struct string_map *options, const char *key,
236      const char *default_value)
237 {
238   return driver_option_get (d, options, key, default_value);
239 }
240
241 static struct output_driver *
242 ascii_create (struct  file_handle *fh, enum settings_output_devices device_type,
243               struct string_map *o)
244 {
245   enum { BOX_ASCII, BOX_UNICODE } box;
246   int min_break[TABLE_N_AXES];
247   struct output_driver *d;
248   struct ascii_driver *a;
249
250   a = xzalloc (sizeof *a);
251   d = &a->driver;
252   output_driver_init (&a->driver, &ascii_driver_class, fh_get_file_name (fh), device_type);
253   a->append = parse_boolean (opt (d, o, "append", "false"));
254   a->emphasis = parse_boolean (opt (d, o, "emphasis", "false"));
255
256   a->chart_file_name = parse_chart_file_name (opt (d, o, "charts", fh_get_file_name (fh)));
257   a->handle = fh;
258
259   min_break[H] = parse_int (opt (d, o, "min-hbreak", "-1"), -1, INT_MAX);
260
261   a->width = parse_page_size (opt (d, o, "width", "79"));
262   a->auto_width = a->width < 0;
263   a->min_break[H] = min_break[H] >= 0 ? min_break[H] : a->width / 2;
264 #ifdef HAVE_CAIRO
265   parse_color (d, o, "background-color", "#FFFFFFFFFFFF", &a->bg);
266   parse_color (d, o, "foreground-color", "#000000000000", &a->fg);
267 #endif
268   box = parse_enum (opt (d, o, "box", "ascii"),
269                     "ascii", BOX_ASCII,
270                     "unicode", BOX_UNICODE,
271                     NULL_SENTINEL);
272   a->box = box == BOX_ASCII ? ascii_box_chars : unicode_box_chars;
273
274   a->file = NULL;
275   a->error = false;
276   a->lines = NULL;
277   a->allocated_lines = 0;
278   a->chart_cnt = 1;
279
280   if (!update_page_size (a, true))
281     goto error;
282
283   return d;
284
285 error:
286   output_driver_destroy (d);
287   return NULL;
288 }
289
290 static int
291 parse_page_size (struct driver_option *option)
292 {
293   int dim = atol (option->default_value);
294
295   if (option->value != NULL)
296     {
297       if (!strcmp (option->value, "auto"))
298         dim = -1;
299       else
300         {
301           int value;
302           char *tail;
303
304           errno = 0;
305           value = strtol (option->value, &tail, 0);
306           if (dim >= 1 && errno != ERANGE && *tail == '\0')
307             dim = value;
308           else
309             msg (MW, _("%s: %s must be positive integer or `auto'"),
310                    option->driver_name, option->name);
311         }
312     }
313
314   driver_option_destroy (option);
315
316   return dim;
317 }
318
319 /* Re-calculates the page width based on settings, margins, and, if "auto" is
320    set, the size of the user's terminal window or GUI output window. */
321 static bool
322 update_page_size (struct ascii_driver *a, bool issue_error)
323 {
324   enum { MIN_WIDTH = 6, MIN_LENGTH = 6 };
325
326   if (a->auto_width)
327     {
328       a->width = settings_get_viewwidth ();
329       a->min_break[H] = a->width / 2;
330     }
331
332   if (a->width < MIN_WIDTH)
333     {
334       if (issue_error)
335         msg (ME,
336                _("ascii: page must be at least %d characters wide, but "
337                  "as configured is only %d characters"),
338                MIN_WIDTH,
339                a->width);
340       if (a->width < MIN_WIDTH)
341         a->width = MIN_WIDTH;
342       return false;
343     }
344
345   return true;
346 }
347
348 static void
349 ascii_destroy (struct output_driver *driver)
350 {
351   struct ascii_driver *a = ascii_driver_cast (driver);
352   int i;
353
354   if (a->file != NULL)
355     fn_close (a->handle, a->file);
356   fh_unref (a->handle);
357   free (a->chart_file_name);
358   for (i = 0; i < a->allocated_lines; i++)
359     u8_line_destroy (&a->lines[i]);
360   free (a->lines);
361   free (a);
362 }
363
364 static void
365 ascii_flush (struct output_driver *driver)
366 {
367   struct ascii_driver *a = ascii_driver_cast (driver);
368   if (a->file)
369     fflush (a->file);
370 }
371
372 static void
373 ascii_output_lines (struct ascii_driver *a, size_t n_lines)
374 {
375   for (size_t y = 0; y < n_lines; y++)
376     {
377       struct u8_line *line = &a->lines[y];
378
379       while (ds_chomp_byte (&line->s, ' '))
380         continue;
381       fwrite (ds_data (&line->s), 1, ds_length (&line->s), a->file);
382       putc ('\n', a->file);
383
384       u8_line_clear (&a->lines[y]);
385     }
386 }
387
388 static void
389 ascii_output_table_item (struct ascii_driver *a,
390                          const struct table_item *table_item)
391 {
392   struct render_params params;
393   struct render_pager *p;
394   int i;
395
396   update_page_size (a, false);
397
398   params.draw_line = ascii_draw_line;
399   params.measure_cell_width = ascii_measure_cell_width;
400   params.measure_cell_height = ascii_measure_cell_height;
401   params.adjust_break = NULL;
402   params.draw_cell = ascii_draw_cell;
403   params.aux = a;
404   params.size[H] = a->width;
405   params.size[V] = INT_MAX;
406   params.font_size[H] = 1;
407   params.font_size[V] = 1;
408   for (i = 0; i < RENDER_N_LINES; i++)
409     {
410       int width = i == RENDER_LINE_NONE ? 0 : 1;
411       params.line_widths[H][i] = width;
412       params.line_widths[V][i] = width;
413     }
414   for (i = 0; i < TABLE_N_AXES; i++)
415     params.min_break[i] = a->min_break[i];
416   params.supports_margins = 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 (&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 (TAB_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       const char *text = text_item_get_text (text_item);
483
484       switch (type)
485         {
486         case TEXT_ITEM_TITLE:
487         case TEXT_ITEM_SUBTITLE:
488         case TEXT_ITEM_COMMAND_OPEN:
489         case TEXT_ITEM_COMMAND_CLOSE:
490           break;
491
492         case TEXT_ITEM_BLANK_LINE:
493           break;
494
495         case TEXT_ITEM_EJECT_PAGE:
496           break;
497
498         default:
499           ascii_output_text (a, text);
500           break;
501         }
502     }
503   else if (is_message_item (output_item))
504     {
505       const struct message_item *message_item = to_message_item (output_item);
506       const struct msg *msg = message_item_get_msg (message_item);
507       char *s = msg_to_string (msg, message_item->command_name);
508       ascii_output_text (a, s);
509       free (s);
510     }
511 }
512
513 const struct output_driver_factory txt_driver_factory =
514   { "txt", "-", ascii_create };
515 const struct output_driver_factory list_driver_factory =
516   { "list", "-", ascii_create };
517
518 static const struct output_driver_class ascii_driver_class =
519   {
520     "text",
521     ascii_destroy,
522     ascii_submit,
523     ascii_flush,
524   };
525 \f
526 static char *ascii_reserve (struct ascii_driver *, int y, int x0, int x1,
527                             int n);
528 static void ascii_layout_cell (struct ascii_driver *,
529                                const struct table_cell *,
530                                int bb[TABLE_N_AXES][2],
531                                int clip[TABLE_N_AXES][2],
532                                int *width, int *height);
533
534 static void
535 ascii_draw_line (void *a_, int bb[TABLE_N_AXES][2],
536                  enum render_line_style styles[TABLE_N_AXES][2])
537 {
538   struct ascii_driver *a = a_;
539   char mbchar[6];
540   int x0, y0, x1, y1;
541   ucs4_t uc;
542   int mblen;
543   int x, y;
544
545   /* Clip to the page. */
546   x0 = MAX (bb[H][0], 0);
547   y0 = MAX (bb[V][0], 0);
548   x1 = MIN (bb[H][1], a->width);
549   y1 = bb[V][1];
550   if (x1 <= 0 || y1 <= 0 || x0 >= a->width)
551     return;
552
553   /* Draw. */
554   uc = a->box[make_box_index (styles[V][0], styles[V][1],
555                               styles[H][0], styles[H][1])];
556   mblen = u8_uctomb (CHAR_CAST (uint8_t *, mbchar), uc, 6);
557   for (y = y0; y < y1; y++)
558     {
559       char *p = ascii_reserve (a, y, x0, x1, mblen * (x1 - x0));
560       for (x = x0; x < x1; x++)
561         {
562           memcpy (p, mbchar, mblen);
563           p += mblen;
564         }
565     }
566 }
567
568 static void
569 ascii_measure_cell_width (void *a_, const struct table_cell *cell,
570                           int *min_width, int *max_width)
571 {
572   struct ascii_driver *a = a_;
573   int bb[TABLE_N_AXES][2];
574   int clip[TABLE_N_AXES][2];
575   int h;
576
577   bb[H][0] = 0;
578   bb[H][1] = INT_MAX;
579   bb[V][0] = 0;
580   bb[V][1] = INT_MAX;
581   clip[H][0] = clip[H][1] = clip[V][0] = clip[V][1] = 0;
582   ascii_layout_cell (a, cell, bb, clip, max_width, &h);
583
584   if (cell->n_contents != 1
585       || cell->contents[0].n_footnotes
586       || strchr (cell->contents[0].text, ' '))
587     {
588       bb[H][1] = 1;
589       ascii_layout_cell (a, cell, bb, clip, min_width, &h);
590     }
591   else
592     *min_width = *max_width;
593 }
594
595 static int
596 ascii_measure_cell_height (void *a_, const struct table_cell *cell, int width)
597 {
598   struct ascii_driver *a = a_;
599   int bb[TABLE_N_AXES][2];
600   int clip[TABLE_N_AXES][2];
601   int w, h;
602
603   bb[H][0] = 0;
604   bb[H][1] = width;
605   bb[V][0] = 0;
606   bb[V][1] = INT_MAX;
607   clip[H][0] = clip[H][1] = clip[V][0] = clip[V][1] = 0;
608   ascii_layout_cell (a, cell, bb, clip, &w, &h);
609   return h;
610 }
611
612 static void
613 ascii_draw_cell (void *a_, const struct table_cell *cell,
614                  int bb[TABLE_N_AXES][2],
615                  int spill[TABLE_N_AXES][2] UNUSED,
616                  int clip[TABLE_N_AXES][2])
617 {
618   struct ascii_driver *a = a_;
619   int w, h;
620
621   ascii_layout_cell (a, cell, bb, clip, &w, &h);
622 }
623
624 static char *
625 ascii_reserve (struct ascii_driver *a, int y, int x0, int x1, int n)
626 {
627   if (y >= a->allocated_lines)
628     {
629       size_t new_alloc = MAX (25, a->allocated_lines);
630       while (new_alloc <= y)
631         new_alloc = xtimes (new_alloc, 2);
632       a->lines = xnrealloc (a->lines, new_alloc, sizeof *a->lines);
633       for (size_t i = a->allocated_lines; i < new_alloc; i++)
634         u8_line_init (&a->lines[i]);
635       a->allocated_lines = new_alloc;
636     }
637   return u8_line_reserve (&a->lines[y], x0, x1, n);
638 }
639
640 static void
641 text_draw (struct ascii_driver *a, unsigned int options,
642            bool bold, bool underline,
643            int bb[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2],
644            int y, const uint8_t *string, int n, size_t width)
645 {
646   int x0 = MAX (0, clip[H][0]);
647   int y0 = MAX (0, clip[V][0]);
648   int x1 = MIN (a->width, clip[H][1]);
649   int y1 = clip[V][1];
650   int x;
651
652   if (y < y0 || y >= y1)
653     return;
654
655   switch (options & TAB_HALIGN)
656     {
657     case TAB_LEFT:
658       x = bb[H][0];
659       break;
660     case TAB_CENTER:
661       x = (bb[H][0] + bb[H][1] - width + 1) / 2;
662       break;
663     case TAB_RIGHT:
664       x = bb[H][1] - width;
665       break;
666     default:
667       NOT_REACHED ();
668     }
669   if (x >= x1)
670     return;
671
672   while (x < x0)
673     {
674       ucs4_t uc;
675       int mblen;
676       int w;
677
678       if (n == 0)
679         return;
680       mblen = u8_mbtouc (&uc, string, n);
681
682       string += mblen;
683       n -= mblen;
684
685       w = uc_width (uc, "UTF-8");
686       if (w > 0)
687         {
688           x += w;
689           width -= w;
690         }
691     }
692   if (n == 0)
693     return;
694
695   if (x + width > x1)
696     {
697       int ofs;
698
699       ofs = width = 0;
700       for (ofs = 0; ofs < n; )
701         {
702           ucs4_t uc;
703           int mblen;
704           int w;
705
706           mblen = u8_mbtouc (&uc, string + ofs, n - ofs);
707
708           w = uc_width (uc, "UTF-8");
709           if (w > 0)
710             {
711               if (width + w > x1 - x)
712                 break;
713               width += w;
714             }
715           ofs += mblen;
716         }
717       n = ofs;
718       if (n == 0)
719         return;
720     }
721
722   if (!a->emphasis || (!bold && !underline))
723     memcpy (ascii_reserve (a, y, x, x + width, n), string, n);
724   else
725     {
726       size_t n_out;
727       size_t ofs;
728       char *out;
729       int mblen;
730
731       /* First figure out how many bytes need to be inserted. */
732       n_out = n;
733       for (ofs = 0; ofs < n; ofs += mblen)
734         {
735           ucs4_t uc;
736           int w;
737
738           mblen = u8_mbtouc (&uc, string + ofs, n - ofs);
739           w = uc_width (uc, "UTF-8");
740
741           if (w > 0)
742             {
743               if (bold)
744                 n_out += 1 + mblen;
745               if (underline)
746                 n_out += 2;
747             }
748         }
749
750       /* Then insert them. */
751       out = ascii_reserve (a, y, x, x + width, n_out);
752       for (ofs = 0; ofs < n; ofs += mblen)
753         {
754           ucs4_t uc;
755           int w;
756
757           mblen = u8_mbtouc (&uc, string + ofs, n - ofs);
758           w = uc_width (uc, "UTF-8");
759
760           if (w > 0)
761             {
762               if (bold)
763                 {
764                   out = mempcpy (out, string + ofs, mblen);
765                   *out++ = '\b';
766                 }
767               if (underline)
768                 {
769                   *out++ = '_';
770                   *out++ = '\b';
771                 }
772             }
773           out = mempcpy (out, string + ofs, mblen);
774         }
775     }
776 }
777
778 static int
779 ascii_layout_cell_text (struct ascii_driver *a,
780                         const struct cell_contents *contents,
781                         bool bold, bool underline,
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, bold, underline,
877                  bb, clip, y, line, graph_ofs, width);
878
879       /* If a new-line ended the line, just skip the new-line.  Otherwise, skip
880          past any spaces past the end of the line (but not past a new-line). */
881       if (b[ofs] == UC_BREAK_MANDATORY)
882         ofs++;
883       else
884         while (ofs < n && isspace (line[ofs]) && b[ofs] != UC_BREAK_MANDATORY)
885           ofs++;
886
887       if (width > *widthp)
888         *widthp = width;
889       pos += ofs;
890     }
891
892   free (breaks);
893   if (text != contents->text)
894     free (CONST_CAST (char *, text));
895
896   return y;
897 }
898
899 static void
900 ascii_layout_cell (struct ascii_driver *a, const struct table_cell *cell,
901                    int bb_[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2],
902                    int *widthp, int *heightp)
903 {
904   int bb[TABLE_N_AXES][2];
905   size_t i;
906
907   *widthp = 0;
908   *heightp = 0;
909
910   memcpy (bb, bb_, sizeof bb);
911   for (i = 0; i < cell->n_contents && bb[V][0] < bb[V][1]; i++)
912     {
913       const struct cell_contents *contents = &cell->contents[i];
914
915       /* Put a blank line between contents. */
916       if (i > 0)
917         {
918           bb[V][0]++;
919           if (bb[V][0] >= bb[V][1])
920             break;
921         }
922
923       bb[V][0] = ascii_layout_cell_text (a, contents, cell->style->bold,
924                                          cell->style->underline,
925                                          bb, clip, widthp);
926     }
927   *heightp = bb[V][0] - bb_[V][0];
928 }
929
930 void
931 ascii_test_write (struct output_driver *driver,
932                   const char *s, int x, int y, bool bold, bool underline)
933 {
934   struct ascii_driver *a = ascii_driver_cast (driver);
935   int bb[TABLE_N_AXES][2];
936   int width, height;
937
938   if (a->file == NULL && !ascii_open_page (a))
939     return;
940
941   struct cell_contents contents = {
942     .options = TAB_LEFT,
943     .text = CONST_CAST (char *, s),
944   };
945   struct cell_style cell_style = {
946     .bold = bold,
947     .underline = underline,
948   };
949   struct table_cell cell = {
950     .contents = &contents,
951     .n_contents = 1,
952     .style = &cell_style,
953   };
954
955   bb[TABLE_HORZ][0] = x;
956   bb[TABLE_HORZ][1] = a->width;
957   bb[TABLE_VERT][0] = y;
958   bb[TABLE_VERT][1] = INT_MAX;
959
960   ascii_layout_cell (a, &cell, bb, bb, &width, &height);
961 }
962
963 void
964 ascii_test_set_length (struct output_driver *driver, int y, int length)
965 {
966   struct ascii_driver *a = ascii_driver_cast (driver);
967
968   if (a->file == NULL && !ascii_open_page (a))
969     return;
970
971   if (y < 0)
972     return;
973   u8_line_set_length (&a->lines[y], length);
974 }
975
976 void
977 ascii_test_flush (struct output_driver *driver)
978 {
979   struct ascii_driver *a = ascii_driver_cast (driver);
980
981   for (size_t i = a->allocated_lines; i-- > 0; )
982     if (a->lines[i].width)
983       {
984         ascii_output_lines (a, i + 1);
985         break;
986       }
987 }
988 \f
989 /* ascii_close_page () and support routines. */
990
991 #if HAVE_DECL_SIGWINCH
992 static struct ascii_driver *the_driver;
993
994 static void
995 winch_handler (int signum UNUSED)
996 {
997   update_page_size (the_driver, false);
998 }
999 #endif
1000
1001 static bool
1002 ascii_open_page (struct ascii_driver *a)
1003 {
1004   if (a->error)
1005     return false;
1006
1007   if (a->file == NULL)
1008     {
1009       a->file = fn_open (a->handle, a->append ? "a" : "w");
1010       if (a->file != NULL)
1011         {
1012           if ( isatty (fileno (a->file)))
1013             {
1014 #if HAVE_DECL_SIGWINCH
1015               struct sigaction action;
1016               sigemptyset (&action.sa_mask);
1017               action.sa_flags = 0;
1018               action.sa_handler = winch_handler;
1019               the_driver = a;
1020               sigaction (SIGWINCH, &action, NULL);
1021 #endif
1022               a->auto_width = true;
1023             }
1024         }
1025       else
1026         {
1027           msg_error (errno, _("ascii: opening output file `%s'"),
1028                      fh_get_file_name (a->handle));
1029           a->error = true;
1030           return false;
1031         }
1032     }
1033
1034   return true;
1035 }