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