output: Support decimal and mixed alignment,
[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     struct render_params params;
206   };
207
208 static const struct output_driver_class ascii_driver_class;
209
210 static void ascii_submit (struct output_driver *, const struct output_item *);
211
212 static bool update_page_size (struct ascii_driver *, bool issue_error);
213 static int parse_page_size (struct driver_option *);
214
215 static bool ascii_open_page (struct ascii_driver *);
216
217 static void ascii_draw_line (void *, int bb[TABLE_N_AXES][2],
218                              enum render_line_style styles[TABLE_N_AXES][2],
219                              struct cell_color colors[TABLE_N_AXES][2]);
220 static void ascii_measure_cell_width (void *, const struct table_cell *,
221                                       int *min, int *max);
222 static int ascii_measure_cell_height (void *, const struct table_cell *,
223                                       int width);
224 static void ascii_draw_cell (void *, const struct table_cell *, int color_idx,
225                              int bb[TABLE_N_AXES][2],
226                              int spill[TABLE_N_AXES][2],
227                              int clip[TABLE_N_AXES][2]);
228
229 static struct ascii_driver *
230 ascii_driver_cast (struct output_driver *driver)
231 {
232   assert (driver->class == &ascii_driver_class);
233   return UP_CAST (driver, struct ascii_driver, driver);
234 }
235
236 static struct driver_option *
237 opt (struct output_driver *d, struct string_map *options, const char *key,
238      const char *default_value)
239 {
240   return driver_option_get (d, options, key, default_value);
241 }
242
243 static struct output_driver *
244 ascii_create (struct  file_handle *fh, enum settings_output_devices device_type,
245               struct string_map *o)
246 {
247   enum { BOX_ASCII, BOX_UNICODE } box;
248   int min_break[TABLE_N_AXES];
249   struct output_driver *d;
250   struct ascii_driver *a;
251
252   a = xzalloc (sizeof *a);
253   d = &a->driver;
254   output_driver_init (&a->driver, &ascii_driver_class, fh_get_file_name (fh), device_type);
255   a->append = parse_boolean (opt (d, o, "append", "false"));
256   a->emphasis = parse_boolean (opt (d, o, "emphasis", "false"));
257
258   a->chart_file_name = parse_chart_file_name (opt (d, o, "charts", fh_get_file_name (fh)));
259   a->handle = fh;
260
261   min_break[H] = parse_int (opt (d, o, "min-hbreak", "-1"), -1, INT_MAX);
262
263   a->width = parse_page_size (opt (d, o, "width", "79"));
264   a->auto_width = a->width < 0;
265   a->min_break[H] = min_break[H] >= 0 ? min_break[H] : a->width / 2;
266 #ifdef HAVE_CAIRO
267   parse_color (d, o, "background-color", "#FFFFFFFFFFFF", &a->bg);
268   parse_color (d, o, "foreground-color", "#000000000000", &a->fg);
269 #endif
270   box = parse_enum (opt (d, o, "box", "ascii"),
271                     "ascii", BOX_ASCII,
272                     "unicode", BOX_UNICODE,
273                     NULL_SENTINEL);
274   a->box = box == BOX_ASCII ? ascii_box_chars : unicode_box_chars;
275
276   a->file = NULL;
277   a->error = false;
278   a->lines = NULL;
279   a->allocated_lines = 0;
280   a->chart_cnt = 1;
281
282   a->params.draw_line = ascii_draw_line;
283   a->params.measure_cell_width = ascii_measure_cell_width;
284   a->params.measure_cell_height = ascii_measure_cell_height;
285   a->params.adjust_break = NULL;
286   a->params.draw_cell = ascii_draw_cell;
287   a->params.aux = a;
288   a->params.size[H] = a->width;
289   a->params.size[V] = INT_MAX;
290   a->params.font_size[H] = 1;
291   a->params.font_size[V] = 1;
292   for (int i = 0; i < RENDER_N_LINES; i++)
293     {
294       int width = i == RENDER_LINE_NONE ? 0 : 1;
295       a->params.line_widths[H][i] = width;
296       a->params.line_widths[V][i] = width;
297     }
298   for (int i = 0; i < TABLE_N_AXES; i++)
299     a->params.min_break[i] = a->min_break[i];
300   a->params.supports_margins = false;
301   a->params.rtl = render_direction_rtl ();
302
303   if (!update_page_size (a, true))
304     goto error;
305
306   return d;
307
308 error:
309   output_driver_destroy (d);
310   return NULL;
311 }
312
313 static int
314 parse_page_size (struct driver_option *option)
315 {
316   int dim = atol (option->default_value);
317
318   if (option->value != NULL)
319     {
320       if (!strcmp (option->value, "auto"))
321         dim = -1;
322       else
323         {
324           int value;
325           char *tail;
326
327           errno = 0;
328           value = strtol (option->value, &tail, 0);
329           if (dim >= 1 && errno != ERANGE && *tail == '\0')
330             dim = value;
331           else
332             msg (MW, _("%s: %s must be positive integer or `auto'"),
333                    option->driver_name, option->name);
334         }
335     }
336
337   driver_option_destroy (option);
338
339   return dim;
340 }
341
342 /* Re-calculates the page width based on settings, margins, and, if "auto" is
343    set, the size of the user's terminal window or GUI output window. */
344 static bool
345 update_page_size (struct ascii_driver *a, bool issue_error)
346 {
347   enum { MIN_WIDTH = 6, MIN_LENGTH = 6 };
348
349   if (a->auto_width)
350     {
351       a->params.size[H] = a->width = settings_get_viewwidth ();
352       a->params.min_break[H] = a->min_break[H] = a->width / 2;
353     }
354
355   if (a->width < MIN_WIDTH)
356     {
357       if (issue_error)
358         msg (ME,
359                _("ascii: page must be at least %d characters wide, but "
360                  "as configured is only %d characters"),
361                MIN_WIDTH,
362                a->width);
363       if (a->width < MIN_WIDTH)
364         a->params.size[H] = a->width = MIN_WIDTH;
365       return false;
366     }
367
368   return true;
369 }
370
371 static void
372 ascii_destroy (struct output_driver *driver)
373 {
374   struct ascii_driver *a = ascii_driver_cast (driver);
375   int i;
376
377   if (a->file != NULL)
378     fn_close (a->handle, a->file);
379   fh_unref (a->handle);
380   free (a->chart_file_name);
381   for (i = 0; i < a->allocated_lines; i++)
382     u8_line_destroy (&a->lines[i]);
383   free (a->lines);
384   free (a);
385 }
386
387 static void
388 ascii_flush (struct output_driver *driver)
389 {
390   struct ascii_driver *a = ascii_driver_cast (driver);
391   if (a->file)
392     fflush (a->file);
393 }
394
395 static void
396 ascii_output_lines (struct ascii_driver *a, size_t n_lines)
397 {
398   for (size_t y = 0; y < n_lines; y++)
399     {
400       struct u8_line *line = &a->lines[y];
401
402       while (ds_chomp_byte (&line->s, ' '))
403         continue;
404       fwrite (ds_data (&line->s), 1, ds_length (&line->s), a->file);
405       putc ('\n', a->file);
406
407       u8_line_clear (&a->lines[y]);
408     }
409 }
410
411 static void
412 ascii_output_table_item (struct ascii_driver *a,
413                          const struct table_item *table_item)
414 {
415   struct render_pager *p;
416
417   update_page_size (a, 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 (&a->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 (TABLE_HALIGN_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
484       switch (type)
485         {
486         case TEXT_ITEM_PAGE_TITLE:
487         case TEXT_ITEM_BLANK_LINE:
488           break;
489
490         case TEXT_ITEM_EJECT_PAGE:
491           break;
492
493         default:
494           ascii_output_table_item (a, text_item_to_table_item (text_item_ref (text_item)));
495           break;
496         }
497     }
498   else if (is_message_item (output_item))
499     {
500       const struct message_item *message_item = to_message_item (output_item);
501       char *s = msg_to_string (message_item_get_msg (message_item));
502       ascii_output_text (a, s);
503       free (s);
504     }
505 }
506
507 const struct output_driver_factory txt_driver_factory =
508   { "txt", "-", ascii_create };
509 const struct output_driver_factory list_driver_factory =
510   { "list", "-", ascii_create };
511
512 static const struct output_driver_class ascii_driver_class =
513   {
514     "text",
515     ascii_destroy,
516     ascii_submit,
517     ascii_flush,
518   };
519 \f
520 static char *ascii_reserve (struct ascii_driver *, int y, int x0, int x1,
521                             int n);
522 static void ascii_layout_cell (struct ascii_driver *,
523                                const struct table_cell *,
524                                int bb[TABLE_N_AXES][2],
525                                int clip[TABLE_N_AXES][2],
526                                int *width, int *height);
527
528 static void
529 ascii_draw_line (void *a_, int bb[TABLE_N_AXES][2],
530                  enum render_line_style styles[TABLE_N_AXES][2],
531                  struct cell_color colors[TABLE_N_AXES][2] UNUSED)
532 {
533   struct ascii_driver *a = a_;
534   char mbchar[6];
535   int x0, y0, x1, y1;
536   ucs4_t uc;
537   int mblen;
538   int x, y;
539
540   /* Clip to the page. */
541   x0 = MAX (bb[H][0], 0);
542   y0 = MAX (bb[V][0], 0);
543   x1 = MIN (bb[H][1], a->width);
544   y1 = bb[V][1];
545   if (x1 <= 0 || y1 <= 0 || x0 >= a->width)
546     return;
547
548   /* Draw. */
549   uc = a->box[make_box_index (styles[V][0], styles[V][1],
550                               styles[H][0], styles[H][1])];
551   mblen = u8_uctomb (CHAR_CAST (uint8_t *, mbchar), uc, 6);
552   for (y = y0; y < y1; y++)
553     {
554       char *p = ascii_reserve (a, y, x0, x1, mblen * (x1 - x0));
555       for (x = x0; x < x1; x++)
556         {
557           memcpy (p, mbchar, mblen);
558           p += mblen;
559         }
560     }
561 }
562
563 static void
564 ascii_measure_cell_width (void *a_, const struct table_cell *cell,
565                           int *min_width, int *max_width)
566 {
567   struct ascii_driver *a = a_;
568   int bb[TABLE_N_AXES][2];
569   int clip[TABLE_N_AXES][2];
570   int h;
571
572   bb[H][0] = 0;
573   bb[H][1] = INT_MAX;
574   bb[V][0] = 0;
575   bb[V][1] = INT_MAX;
576   clip[H][0] = clip[H][1] = clip[V][0] = clip[V][1] = 0;
577   ascii_layout_cell (a, cell, bb, clip, max_width, &h);
578
579   if (cell->n_footnotes || strchr (cell->text, ' '))
580     {
581       bb[H][1] = 1;
582       ascii_layout_cell (a, cell, bb, clip, min_width, &h);
583     }
584   else
585     *min_width = *max_width;
586 }
587
588 static int
589 ascii_measure_cell_height (void *a_, const struct table_cell *cell, int width)
590 {
591   struct ascii_driver *a = a_;
592   int bb[TABLE_N_AXES][2];
593   int clip[TABLE_N_AXES][2];
594   int w, h;
595
596   bb[H][0] = 0;
597   bb[H][1] = width;
598   bb[V][0] = 0;
599   bb[V][1] = INT_MAX;
600   clip[H][0] = clip[H][1] = clip[V][0] = clip[V][1] = 0;
601   ascii_layout_cell (a, cell, bb, clip, &w, &h);
602   return h;
603 }
604
605 static void
606 ascii_draw_cell (void *a_, const struct table_cell *cell, int color_idx UNUSED,
607                  int bb[TABLE_N_AXES][2],
608                  int spill[TABLE_N_AXES][2] UNUSED,
609                  int clip[TABLE_N_AXES][2])
610 {
611   struct ascii_driver *a = a_;
612   int w, h;
613
614   ascii_layout_cell (a, cell, bb, clip, &w, &h);
615 }
616
617 static char *
618 ascii_reserve (struct ascii_driver *a, int y, int x0, int x1, int n)
619 {
620   if (y >= a->allocated_lines)
621     {
622       size_t new_alloc = MAX (25, a->allocated_lines);
623       while (new_alloc <= y)
624         new_alloc = xtimes (new_alloc, 2);
625       a->lines = xnrealloc (a->lines, new_alloc, sizeof *a->lines);
626       for (size_t i = a->allocated_lines; i < new_alloc; i++)
627         u8_line_init (&a->lines[i]);
628       a->allocated_lines = new_alloc;
629     }
630   return u8_line_reserve (&a->lines[y], x0, x1, n);
631 }
632
633 static void
634 text_draw (struct ascii_driver *a, enum table_halign halign, int options,
635            bool bold, bool underline,
636            int bb[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2],
637            int y, const uint8_t *string, int n, size_t width)
638 {
639   int x0 = MAX (0, clip[H][0]);
640   int y0 = MAX (0, clip[V][0]);
641   int x1 = MIN (a->width, clip[H][1]);
642   int y1 = clip[V][1];
643   int x;
644
645   if (y < y0 || y >= y1)
646     return;
647
648   switch (table_halign_interpret (halign, options & TAB_NUMERIC))
649     {
650     case TABLE_HALIGN_LEFT:
651       x = bb[H][0];
652       break;
653     case TABLE_HALIGN_CENTER:
654       x = (bb[H][0] + bb[H][1] - width + 1) / 2;
655       break;
656     case TABLE_HALIGN_RIGHT:
657     case TABLE_HALIGN_DECIMAL:
658       x = bb[H][1] - width;
659       break;
660     default:
661       NOT_REACHED ();
662     }
663   if (x >= x1)
664     return;
665
666   while (x < x0)
667     {
668       ucs4_t uc;
669       int mblen;
670       int w;
671
672       if (n == 0)
673         return;
674       mblen = u8_mbtouc (&uc, string, n);
675
676       string += mblen;
677       n -= mblen;
678
679       w = uc_width (uc, "UTF-8");
680       if (w > 0)
681         {
682           x += w;
683           width -= w;
684         }
685     }
686   if (n == 0)
687     return;
688
689   if (x + width > x1)
690     {
691       int ofs;
692
693       ofs = width = 0;
694       for (ofs = 0; ofs < n; )
695         {
696           ucs4_t uc;
697           int mblen;
698           int w;
699
700           mblen = u8_mbtouc (&uc, string + ofs, n - ofs);
701
702           w = uc_width (uc, "UTF-8");
703           if (w > 0)
704             {
705               if (width + w > x1 - x)
706                 break;
707               width += w;
708             }
709           ofs += mblen;
710         }
711       n = ofs;
712       if (n == 0)
713         return;
714     }
715
716   if (!a->emphasis || (!bold && !underline))
717     memcpy (ascii_reserve (a, y, x, x + width, n), string, n);
718   else
719     {
720       size_t n_out;
721       size_t ofs;
722       char *out;
723       int mblen;
724
725       /* First figure out how many bytes need to be inserted. */
726       n_out = n;
727       for (ofs = 0; ofs < n; ofs += mblen)
728         {
729           ucs4_t uc;
730           int w;
731
732           mblen = u8_mbtouc (&uc, string + ofs, n - ofs);
733           w = uc_width (uc, "UTF-8");
734
735           if (w > 0)
736             {
737               if (bold)
738                 n_out += 1 + mblen;
739               if (underline)
740                 n_out += 2;
741             }
742         }
743
744       /* Then insert them. */
745       out = ascii_reserve (a, y, x, x + width, n_out);
746       for (ofs = 0; ofs < n; ofs += mblen)
747         {
748           ucs4_t uc;
749           int w;
750
751           mblen = u8_mbtouc (&uc, string + ofs, n - ofs);
752           w = uc_width (uc, "UTF-8");
753
754           if (w > 0)
755             {
756               if (bold)
757                 {
758                   out = mempcpy (out, string + ofs, mblen);
759                   *out++ = '\b';
760                 }
761               if (underline)
762                 {
763                   *out++ = '_';
764                   *out++ = '\b';
765                 }
766             }
767           out = mempcpy (out, string + ofs, mblen);
768         }
769     }
770 }
771
772 static char *
773 add_footnote_markers (const char *text, const struct table_cell *cell)
774 {
775   struct string s = DS_EMPTY_INITIALIZER;
776   ds_put_cstr (&s, text);
777   for (size_t i = 0; i < cell->n_footnotes; i++)
778     ds_put_format (&s, "[%s]", cell->footnotes[i]->marker);
779   return ds_steal_cstr (&s);
780 }
781
782 static void
783 ascii_layout_cell (struct ascii_driver *a, const struct table_cell *cell,
784                    int bb[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2],
785                    int *widthp, int *heightp)
786 {
787   *widthp = 0;
788   *heightp = 0;
789
790   /* Get the basic textual contents. */
791   const char *plain_text = (cell->options & TAB_MARKUP
792                             ? output_get_text_from_markup (cell->text)
793                             : cell->text);
794
795   /* Append footnote markers if any. */
796   const char *text;
797   if (cell->n_footnotes)
798     {
799       text = add_footnote_markers (plain_text, cell);
800       if (plain_text != cell->text)
801         free (CONST_CAST (char *, plain_text));
802     }
803   else
804     text = plain_text;
805
806   /* Calculate length; if it's zero, then there's nothing to do. */
807   size_t length = strlen (text);
808   if (!length)
809     {
810       if (text != cell->text)
811         free (CONST_CAST (char *, text));
812       return;
813     }
814
815   char *breaks = xmalloc (length + 1);
816   u8_possible_linebreaks (CHAR_CAST (const uint8_t *, text), length,
817                           "UTF-8", breaks);
818   breaks[length] = (breaks[length - 1] == UC_BREAK_MANDATORY
819                     ? UC_BREAK_PROHIBITED : UC_BREAK_POSSIBLE);
820
821   size_t pos = 0;
822   int bb_width = bb[H][1] - bb[H][0];
823   for (int y = bb[V][0]; y < bb[V][1] && pos < length; y++)
824     {
825       const uint8_t *line = CHAR_CAST (const uint8_t *, text + pos);
826       const char *b = breaks + pos;
827       size_t n = length - pos;
828
829       size_t last_break_ofs = 0;
830       int last_break_width = 0;
831       int width = 0;
832       size_t graph_ofs;
833       size_t ofs;
834
835       for (ofs = 0; ofs < n; )
836         {
837           ucs4_t uc;
838           int mblen;
839           int w;
840
841           mblen = u8_mbtouc (&uc, line + ofs, n - ofs);
842           if (b[ofs] == UC_BREAK_MANDATORY)
843             break;
844           else if (b[ofs] == UC_BREAK_POSSIBLE)
845             {
846               last_break_ofs = ofs;
847               last_break_width = width;
848             }
849
850           w = uc_width (uc, "UTF-8");
851           if (w > 0)
852             {
853               if (width + w > bb_width)
854                 {
855                   if (isspace (line[ofs]))
856                     break;
857                   else if (last_break_ofs != 0)
858                     {
859                       ofs = last_break_ofs;
860                       width = last_break_width;
861                       break;
862                     }
863                 }
864               width += w;
865             }
866           ofs += mblen;
867         }
868
869       /* Trim any trailing spaces off the end of the text to be drawn. */
870       for (graph_ofs = ofs; graph_ofs > 0; graph_ofs--)
871         if (!isspace (line[graph_ofs - 1]))
872           break;
873       width -= ofs - graph_ofs;
874
875       /* Draw text. */
876       text_draw (a, cell->style->cell_style.halign, cell->options,
877                  cell->style->font_style.bold,
878                  cell->style->font_style.underline,
879                  bb, clip, y, line, graph_ofs, width);
880
881       /* If a new-line ended the line, just skip the new-line.  Otherwise, skip
882          past any spaces past the end of the line (but not past a new-line). */
883       if (b[ofs] == UC_BREAK_MANDATORY)
884         ofs++;
885       else
886         while (ofs < n && isspace (line[ofs]) && b[ofs] != UC_BREAK_MANDATORY)
887           ofs++;
888
889       if (width > *widthp)
890         *widthp = width;
891       ++*heightp;
892       pos += ofs;
893     }
894
895   free (breaks);
896   if (text != cell->text)
897     free (CONST_CAST (char *, text));
898 }
899
900 void
901 ascii_test_write (struct output_driver *driver,
902                   const char *s, int x, int y, bool bold, bool underline)
903 {
904   struct ascii_driver *a = ascii_driver_cast (driver);
905   int bb[TABLE_N_AXES][2];
906   int width, height;
907
908   if (a->file == NULL && !ascii_open_page (a))
909     return;
910
911   struct area_style style = {
912     .font_style.bold = bold,
913     .font_style.underline = underline,
914   };
915   struct table_cell cell = {
916     .options = TAB_LEFT,
917     .text = CONST_CAST (char *, s),
918     .style = &style,
919   };
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, 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 }