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