table: Delete some functions that were unused or barely used.
[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_submit (struct output_driver *driver,
486               const struct output_item *output_item)
487 {
488   struct ascii_driver *a = ascii_driver_cast (driver);
489
490   if (a->error)
491     return;
492
493   if (is_table_item (output_item))
494     ascii_output_table_item (a, to_table_item (output_item));
495 #ifdef HAVE_CAIRO
496   else if (is_chart_item (output_item) && a->chart_file_name != NULL)
497     {
498       struct chart_item *chart_item = to_chart_item (output_item);
499       char *file_name;
500
501       file_name = xr_draw_png_chart (chart_item, a->chart_file_name,
502                                      ++a->chart_cnt,
503                                      &a->fg,
504                                      &a->bg);
505       if (file_name != NULL)
506         {
507           struct text_item *text_item;
508
509           text_item = text_item_create_format (
510             TEXT_ITEM_LOG, _("See %s for a chart."), file_name);
511
512           ascii_submit (driver, &text_item->output_item);
513           text_item_unref (text_item);
514           free (file_name);
515         }
516     }
517 #endif  /* HAVE_CAIRO */
518   else if (is_text_item (output_item))
519     {
520       const struct text_item *text_item = to_text_item (output_item);
521       enum text_item_type type = text_item_get_type (text_item);
522
523       if (type != TEXT_ITEM_PAGE_TITLE)
524         ascii_output_table_item_unref (
525           a, text_item_to_table_item (text_item_ref (text_item)));
526     }
527   else if (is_message_item (output_item))
528     ascii_output_table_item_unref (
529       a, text_item_to_table_item (
530         message_item_to_text_item (
531           to_message_item (
532             output_item_ref (output_item)))));
533 }
534
535 const struct output_driver_factory txt_driver_factory =
536   { "txt", "-", ascii_create };
537 const struct output_driver_factory list_driver_factory =
538   { "list", "-", ascii_create };
539
540 static const struct output_driver_class ascii_driver_class =
541   {
542     "text",
543     ascii_destroy,
544     ascii_submit,
545     ascii_flush,
546   };
547 \f
548 static char *ascii_reserve (struct ascii_driver *, int y, int x0, int x1,
549                             int n);
550 static void ascii_layout_cell (struct ascii_driver *,
551                                const struct table_cell *,
552                                int bb[TABLE_N_AXES][2],
553                                int clip[TABLE_N_AXES][2],
554                                int *width, int *height);
555
556 static void
557 ascii_draw_line (void *a_, int bb[TABLE_N_AXES][2],
558                  enum render_line_style styles[TABLE_N_AXES][2],
559                  struct cell_color colors[TABLE_N_AXES][2] UNUSED)
560 {
561   struct ascii_driver *a = a_;
562   char mbchar[6];
563   int x0, y0, x1, y1;
564   ucs4_t uc;
565   int mblen;
566   int x, y;
567
568   /* Clip to the page. */
569   x0 = MAX (bb[H][0], 0);
570   y0 = MAX (bb[V][0], 0);
571   x1 = MIN (bb[H][1], a->width);
572   y1 = bb[V][1];
573   if (x1 <= 0 || y1 <= 0 || x0 >= a->width)
574     return;
575
576   /* Draw. */
577   uc = a->box[make_box_index (styles[V][0], styles[V][1],
578                               styles[H][0], styles[H][1])];
579   mblen = u8_uctomb (CHAR_CAST (uint8_t *, mbchar), uc, 6);
580   for (y = y0; y < y1; y++)
581     {
582       char *p = ascii_reserve (a, y, x0, x1, mblen * (x1 - x0));
583       for (x = x0; x < x1; x++)
584         {
585           memcpy (p, mbchar, mblen);
586           p += mblen;
587         }
588     }
589 }
590
591 static void
592 ascii_measure_cell_width (void *a_, const struct table_cell *cell,
593                           int *min_width, int *max_width)
594 {
595   struct ascii_driver *a = a_;
596   int bb[TABLE_N_AXES][2];
597   int clip[TABLE_N_AXES][2];
598   int h;
599
600   bb[H][0] = 0;
601   bb[H][1] = INT_MAX;
602   bb[V][0] = 0;
603   bb[V][1] = INT_MAX;
604   clip[H][0] = clip[H][1] = clip[V][0] = clip[V][1] = 0;
605   ascii_layout_cell (a, cell, bb, clip, max_width, &h);
606
607   if (cell->n_footnotes || strchr (cell->text, ' ')
608       || cell->n_subscripts || cell->superscript)
609     {
610       bb[H][1] = 1;
611       ascii_layout_cell (a, cell, bb, clip, min_width, &h);
612     }
613   else
614     *min_width = *max_width;
615 }
616
617 static int
618 ascii_measure_cell_height (void *a_, const struct table_cell *cell, int width)
619 {
620   struct ascii_driver *a = a_;
621   int bb[TABLE_N_AXES][2];
622   int clip[TABLE_N_AXES][2];
623   int w, h;
624
625   bb[H][0] = 0;
626   bb[H][1] = width;
627   bb[V][0] = 0;
628   bb[V][1] = INT_MAX;
629   clip[H][0] = clip[H][1] = clip[V][0] = clip[V][1] = 0;
630   ascii_layout_cell (a, cell, bb, clip, &w, &h);
631   return h;
632 }
633
634 static void
635 ascii_draw_cell (void *a_, const struct table_cell *cell, int color_idx UNUSED,
636                  int bb[TABLE_N_AXES][2], int valign_offset,
637                  int spill[TABLE_N_AXES][2] UNUSED,
638                  int clip[TABLE_N_AXES][2])
639 {
640   struct ascii_driver *a = a_;
641   int w, h;
642
643   bb[V][0] += valign_offset;
644   ascii_layout_cell (a, cell, bb, clip, &w, &h);
645 }
646
647 static char *
648 ascii_reserve (struct ascii_driver *a, int y, int x0, int x1, int n)
649 {
650   if (y >= a->allocated_lines)
651     {
652       size_t new_alloc = MAX (25, a->allocated_lines);
653       while (new_alloc <= y)
654         new_alloc = xtimes (new_alloc, 2);
655       a->lines = xnrealloc (a->lines, new_alloc, sizeof *a->lines);
656       for (size_t i = a->allocated_lines; i < new_alloc; i++)
657         u8_line_init (&a->lines[i]);
658       a->allocated_lines = new_alloc;
659     }
660   return u8_line_reserve (&a->lines[y], x0, x1, n);
661 }
662
663 static void
664 text_draw (struct ascii_driver *a, enum table_halign halign, int options,
665            bool bold, bool underline,
666            int bb[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2],
667            int y, const uint8_t *string, int n, size_t width)
668 {
669   int x0 = MAX (0, clip[H][0]);
670   int y0 = MAX (0, clip[V][0]);
671   int x1 = MIN (a->width, clip[H][1]);
672   int y1 = clip[V][1];
673   int x;
674
675   if (y < y0 || y >= y1)
676     return;
677
678   switch (table_halign_interpret (halign, options & TAB_NUMERIC))
679     {
680     case TABLE_HALIGN_LEFT:
681       x = bb[H][0];
682       break;
683     case TABLE_HALIGN_CENTER:
684       x = (bb[H][0] + bb[H][1] - width + 1) / 2;
685       break;
686     case TABLE_HALIGN_RIGHT:
687     case TABLE_HALIGN_DECIMAL:
688       x = bb[H][1] - width;
689       break;
690     default:
691       NOT_REACHED ();
692     }
693   if (x >= x1)
694     return;
695
696   while (x < x0)
697     {
698       ucs4_t uc;
699       int mblen;
700       int w;
701
702       if (n == 0)
703         return;
704       mblen = u8_mbtouc (&uc, string, n);
705
706       string += mblen;
707       n -= mblen;
708
709       w = uc_width (uc, "UTF-8");
710       if (w > 0)
711         {
712           x += w;
713           width -= w;
714         }
715     }
716   if (n == 0)
717     return;
718
719   if (x + width > x1)
720     {
721       int ofs;
722
723       ofs = width = 0;
724       for (ofs = 0; ofs < n;)
725         {
726           ucs4_t uc;
727           int mblen;
728           int w;
729
730           mblen = u8_mbtouc (&uc, string + ofs, n - ofs);
731
732           w = uc_width (uc, "UTF-8");
733           if (w > 0)
734             {
735               if (width + w > x1 - x)
736                 break;
737               width += w;
738             }
739           ofs += mblen;
740         }
741       n = ofs;
742       if (n == 0)
743         return;
744     }
745
746   if (!a->emphasis || (!bold && !underline))
747     memcpy (ascii_reserve (a, y, x, x + width, n), string, n);
748   else
749     {
750       size_t n_out;
751       size_t ofs;
752       char *out;
753       int mblen;
754
755       /* First figure out how many bytes need to be inserted. */
756       n_out = n;
757       for (ofs = 0; ofs < n; ofs += mblen)
758         {
759           ucs4_t uc;
760           int w;
761
762           mblen = u8_mbtouc (&uc, string + ofs, n - ofs);
763           w = uc_width (uc, "UTF-8");
764
765           if (w > 0)
766             {
767               if (bold)
768                 n_out += 1 + mblen;
769               if (underline)
770                 n_out += 2;
771             }
772         }
773
774       /* Then insert them. */
775       out = ascii_reserve (a, y, x, x + width, n_out);
776       for (ofs = 0; ofs < n; ofs += mblen)
777         {
778           ucs4_t uc;
779           int w;
780
781           mblen = u8_mbtouc (&uc, string + ofs, n - ofs);
782           w = uc_width (uc, "UTF-8");
783
784           if (w > 0)
785             {
786               if (bold)
787                 {
788                   out = mempcpy (out, string + ofs, mblen);
789                   *out++ = '\b';
790                 }
791               if (underline)
792                 {
793                   *out++ = '_';
794                   *out++ = '\b';
795                 }
796             }
797           out = mempcpy (out, string + ofs, mblen);
798         }
799     }
800 }
801
802 static char *
803 add_markers (const char *text, const struct table_cell *cell)
804 {
805   struct string s = DS_EMPTY_INITIALIZER;
806   ds_put_cstr (&s, text);
807   for (size_t i = 0; i < cell->n_subscripts; i++)
808     ds_put_format (&s, "%c%s", i ? ',' : '_', cell->subscripts[i]);
809   if (cell->superscript)
810     ds_put_format (&s, "^%s", cell->superscript);
811   for (size_t i = 0; i < cell->n_footnotes; i++)
812     ds_put_format (&s, "[%s]", cell->footnotes[i]->marker);
813   return ds_steal_cstr (&s);
814 }
815
816 static void
817 ascii_layout_cell (struct ascii_driver *a, const struct table_cell *cell,
818                    int bb[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2],
819                    int *widthp, int *heightp)
820 {
821   *widthp = 0;
822   *heightp = 0;
823
824   /* Get the basic textual contents. */
825   const char *plain_text = (cell->options & TAB_MARKUP
826                             ? output_get_text_from_markup (cell->text)
827                             : cell->text);
828
829   /* Append footnotes, subscripts, superscript if any. */
830   const char *text;
831   if (cell->n_footnotes || cell->n_subscripts || cell->superscript)
832     {
833       text = add_markers (plain_text, cell);
834       if (plain_text != cell->text)
835         free (CONST_CAST (char *, plain_text));
836     }
837   else
838     text = plain_text;
839
840   /* Calculate length; if it's zero, then there's nothing to do. */
841   size_t length = strlen (text);
842   if (!length)
843     {
844       if (text != cell->text)
845         free (CONST_CAST (char *, text));
846       return;
847     }
848
849   char *breaks = xmalloc (length + 1);
850   u8_possible_linebreaks (CHAR_CAST (const uint8_t *, text), length,
851                           "UTF-8", breaks);
852   breaks[length] = (breaks[length - 1] == UC_BREAK_MANDATORY
853                     ? UC_BREAK_PROHIBITED : UC_BREAK_POSSIBLE);
854
855   size_t pos = 0;
856   int bb_width = bb[H][1] - bb[H][0];
857   for (int y = bb[V][0]; y < bb[V][1] && pos < length; y++)
858     {
859       const uint8_t *line = CHAR_CAST (const uint8_t *, text + pos);
860       const char *b = breaks + pos;
861       size_t n = length - pos;
862
863       size_t last_break_ofs = 0;
864       int last_break_width = 0;
865       int width = 0;
866       size_t graph_ofs;
867       size_t ofs;
868
869       for (ofs = 0; ofs < n;)
870         {
871           ucs4_t uc;
872           int mblen;
873           int w;
874
875           mblen = u8_mbtouc (&uc, line + ofs, n - ofs);
876           if (b[ofs] == UC_BREAK_MANDATORY)
877             break;
878           else if (b[ofs] == UC_BREAK_POSSIBLE)
879             {
880               last_break_ofs = ofs;
881               last_break_width = width;
882             }
883
884           w = uc_width (uc, "UTF-8");
885           if (w > 0)
886             {
887               if (width + w > bb_width)
888                 {
889                   if (isspace (line[ofs]))
890                     break;
891                   else if (last_break_ofs != 0)
892                     {
893                       ofs = last_break_ofs;
894                       width = last_break_width;
895                       break;
896                     }
897                 }
898               width += w;
899             }
900           ofs += mblen;
901         }
902
903       /* Trim any trailing spaces off the end of the text to be drawn. */
904       for (graph_ofs = ofs; graph_ofs > 0; graph_ofs--)
905         if (!isspace (line[graph_ofs - 1]))
906           break;
907       width -= ofs - graph_ofs;
908
909       /* Draw text. */
910       text_draw (a, cell->style->cell_style.halign, cell->options,
911                  cell->style->font_style.bold,
912                  cell->style->font_style.underline,
913                  bb, clip, y, line, graph_ofs, width);
914
915       /* If a new-line ended the line, just skip the new-line.  Otherwise, skip
916          past any spaces past the end of the line (but not past a new-line). */
917       if (b[ofs] == UC_BREAK_MANDATORY)
918         ofs++;
919       else
920         while (ofs < n && isspace (line[ofs]) && b[ofs] != UC_BREAK_MANDATORY)
921           ofs++;
922
923       if (width > *widthp)
924         *widthp = width;
925       ++*heightp;
926       pos += ofs;
927     }
928
929   free (breaks);
930   if (text != cell->text)
931     free (CONST_CAST (char *, text));
932 }
933
934 void
935 ascii_test_write (struct output_driver *driver,
936                   const char *s, int x, int y, bool bold, bool underline)
937 {
938   struct ascii_driver *a = ascii_driver_cast (driver);
939   int bb[TABLE_N_AXES][2];
940   int width, height;
941
942   if (!a->file)
943     return;
944
945   struct table_area_style style = {
946     .cell_style.halign = TABLE_HALIGN_LEFT,
947     .font_style.bold = bold,
948     .font_style.underline = underline,
949   };
950   struct table_cell cell = {
951     .text = CONST_CAST (char *, s),
952     .style = &style,
953   };
954
955   bb[TABLE_HORZ][0] = x;
956   bb[TABLE_HORZ][1] = a->width;
957   bb[TABLE_VERT][0] = y;
958   bb[TABLE_VERT][1] = INT_MAX;
959
960   ascii_layout_cell (a, &cell, bb, bb, &width, &height);
961 }
962
963 void
964 ascii_test_set_length (struct output_driver *driver, int y, int length)
965 {
966   struct ascii_driver *a = ascii_driver_cast (driver);
967
968   if (!a->file)
969     return;
970
971   if (y < 0)
972     return;
973   u8_line_set_length (&a->lines[y], length);
974 }
975
976 void
977 ascii_test_flush (struct output_driver *driver)
978 {
979   struct ascii_driver *a = ascii_driver_cast (driver);
980
981   for (size_t i = a->allocated_lines; i-- > 0;)
982     if (a->lines[i].width)
983       {
984         ascii_output_lines (a, i + 1);
985         break;
986       }
987 }
988 \f
989 static sig_atomic_t terminal_changed = true;
990 static int terminal_width;
991
992 #if HAVE_DECL_SIGWINCH
993 static void
994 winch_handler (int signum UNUSED)
995 {
996   terminal_changed = true;
997 }
998 #endif
999
1000 int
1001 get_terminal_width (void)
1002 {
1003 #if HAVE_DECL_SIGWINCH
1004   static bool setup_signal;
1005   if (!setup_signal)
1006     {
1007       setup_signal = true;
1008
1009       struct sigaction action = { .sa_handler = winch_handler };
1010       sigemptyset (&action.sa_mask);
1011       sigaction (SIGWINCH, &action, NULL);
1012     }
1013 #endif
1014
1015   if (terminal_changed)
1016     {
1017       terminal_changed = false;
1018
1019 #ifdef HAVE_TERMIOS_H
1020       struct winsize ws;
1021       if (!ioctl (0, TIOCGWINSZ, &ws))
1022         terminal_width = ws.ws_col;
1023       else
1024 #endif
1025         {
1026           if (getenv ("COLUMNS"))
1027             terminal_width = atoi (getenv ("COLUMNS"));
1028         }
1029
1030       if (terminal_width <= 0 || terminal_width > 1024)
1031         terminal_width = 79;
1032     }
1033
1034   return terminal_width;
1035 }