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