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