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