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