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