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