cairo: Move chart code into cairo-chart.
[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 #include "output/cairo-chart.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
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
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], int valign_offset,
239                              int spill[TABLE_N_AXES][2],
240                              int clip[TABLE_N_AXES][2]);
241
242 static struct ascii_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 *
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
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 *
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   a->bg = parse_color (opt (d, o, "background-color", "#FFFFFFFFFFFF"));
296   a->fg = parse_color (opt (d, o, "foreground-color", "#000000000000"));
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   static const struct render_ops ascii_render_ops = {
316     .draw_line = ascii_draw_line,
317     .measure_cell_width = ascii_measure_cell_width,
318     .measure_cell_height = ascii_measure_cell_height,
319     .adjust_break = NULL,
320     .draw_cell = ascii_draw_cell,
321   };
322   a->params.ops = &ascii_render_ops;
323   a->params.aux = a;
324   a->params.size[H] = a->width;
325   a->params.size[V] = INT_MAX;
326   a->params.font_size[H] = 1;
327   a->params.font_size[V] = 1;
328
329   static const int ascii_line_widths[RENDER_N_LINES] = {
330     [RENDER_LINE_NONE] = 0,
331     [RENDER_LINE_SINGLE] = 1,
332     [RENDER_LINE_DASHED] = 1,
333     [RENDER_LINE_THICK] = 1,
334     [RENDER_LINE_THIN] = 1,
335     [RENDER_LINE_DOUBLE] = 1,
336   };
337   a->params.line_widths = ascii_line_widths;
338   a->params.supports_margins = false;
339   a->params.rtl = render_direction_rtl ();
340
341   if (!update_page_size (a, true))
342     goto error;
343
344   a->file = fn_open (a->handle, a->append ? "a" : "w");
345   if (!a->file)
346     {
347       msg_error (errno, _("ascii: opening output file `%s'"),
348                  fh_get_file_name (a->handle));
349       goto error;
350     }
351
352   return d;
353
354 error:
355   output_driver_destroy (d);
356   return NULL;
357 }
358
359 static int
360 parse_page_size (struct driver_option *option)
361 {
362   int dim = atol (option->default_value);
363
364   if (option->value != NULL)
365     {
366       if (!strcmp (option->value, "auto"))
367         dim = -1;
368       else
369         {
370           int value;
371           char *tail;
372
373           errno = 0;
374           value = strtol (option->value, &tail, 0);
375           if (value >= 1 && errno != ERANGE && *tail == '\0')
376             dim = value;
377           else
378             msg (MW, _("%s: %s must be positive integer or `auto'"),
379                    option->driver_name, option->name);
380         }
381     }
382
383   driver_option_destroy (option);
384
385   return dim;
386 }
387
388 /* Re-calculates the page width based on settings, margins, and, if "auto" is
389    set, the size of the user's terminal window or GUI output window. */
390 static bool
391 update_page_size (struct ascii_driver *a, bool issue_error)
392 {
393   enum { MIN_WIDTH = 6 };
394
395   int want_width = (a->width_mode == VIEW_WIDTH ? settings_get_viewwidth ()
396                     : a->width_mode == TERMINAL_WIDTH ? get_terminal_width ()
397                     : a->width);
398   bool ok = want_width >= MIN_WIDTH;
399   if (!ok && issue_error)
400     msg (ME, _("ascii: page must be at least %d characters wide, but "
401                "as configured is only %d characters"),
402          MIN_WIDTH, want_width);
403
404   a->width = ok ? want_width : MIN_WIDTH;
405   a->params.size[H] = a->width;
406   a->params.min_break[H] = a->min_hbreak >= 0 ? a->min_hbreak : a->width / 2;
407
408   return ok;
409 }
410
411 static void
412 ascii_destroy (struct output_driver *driver)
413 {
414   struct ascii_driver *a = ascii_driver_cast (driver);
415   int i;
416
417   if (a->file != NULL)
418     fn_close (a->handle, a->file);
419   fh_unref (a->handle);
420   free (a->chart_file_name);
421   for (i = 0; i < a->allocated_lines; i++)
422     u8_line_destroy (&a->lines[i]);
423   free (a->lines);
424   free (a);
425 }
426
427 static void
428 ascii_flush (struct output_driver *driver)
429 {
430   struct ascii_driver *a = ascii_driver_cast (driver);
431   if (a->file)
432     fflush (a->file);
433 }
434
435 static void
436 ascii_output_lines (struct ascii_driver *a, size_t n_lines)
437 {
438   for (size_t y = 0; y < n_lines; y++)
439     {
440       if (y < a->allocated_lines)
441         {
442           struct u8_line *line = &a->lines[y];
443
444           while (ds_chomp_byte (&line->s, ' '))
445             continue;
446           fwrite (ds_data (&line->s), 1, ds_length (&line->s), a->file);
447           u8_line_clear (&a->lines[y]);
448         }
449       putc ('\n', a->file);
450     }
451 }
452
453 static void
454 ascii_output_table_item (struct ascii_driver *a,
455                          const struct table_item *table_item)
456 {
457   struct render_pager *p;
458
459   update_page_size (a, false);
460
461   if (a->object_cnt++)
462     putc ('\n', a->file);
463
464   p = render_pager_create (&a->params, table_item);
465   for (int i = 0; render_pager_has_next (p); i++)
466     {
467       if (i)
468         putc ('\n', a->file);
469       ascii_output_lines (a, render_pager_draw_next (p, INT_MAX));
470     }
471   render_pager_destroy (p);
472 }
473
474 static void
475 ascii_output_table_item_unref (struct ascii_driver *a,
476                                struct table_item *table_item)
477 {
478   ascii_output_table_item (a, table_item);
479   table_item_unref (table_item);
480 }
481
482 static void
483 ascii_output_text (struct ascii_driver *a, const char *text)
484 {
485   ascii_output_table_item_unref (
486     a, table_item_create (table_from_string (text), NULL, NULL));
487 }
488
489 static void
490 ascii_submit (struct output_driver *driver,
491               const struct output_item *output_item)
492 {
493   struct ascii_driver *a = ascii_driver_cast (driver);
494
495   if (a->error)
496     return;
497
498   if (is_table_item (output_item))
499     ascii_output_table_item (a, to_table_item (output_item));
500 #ifdef HAVE_CAIRO
501   else if (is_chart_item (output_item) && a->chart_file_name != NULL)
502     {
503       struct chart_item *chart_item = to_chart_item (output_item);
504       char *file_name;
505
506       file_name = xr_draw_png_chart (chart_item, a->chart_file_name,
507                                      ++a->chart_cnt,
508                                      &a->fg,
509                                      &a->bg);
510       if (file_name != NULL)
511         {
512           struct text_item *text_item;
513
514           text_item = text_item_create_format (
515             TEXT_ITEM_LOG, _("See %s for a chart."), file_name);
516
517           ascii_submit (driver, &text_item->output_item);
518           text_item_unref (text_item);
519           free (file_name);
520         }
521     }
522 #endif  /* HAVE_CAIRO */
523   else if (is_text_item (output_item))
524     {
525       const struct text_item *text_item = to_text_item (output_item);
526       enum text_item_type type = text_item_get_type (text_item);
527
528       if (type != TEXT_ITEM_PAGE_TITLE)
529         ascii_output_table_item_unref (
530           a, text_item_to_table_item (text_item_ref (text_item)));
531     }
532   else if (is_message_item (output_item))
533     {
534       const struct message_item *message_item = to_message_item (output_item);
535       char *s = msg_to_string (message_item_get_msg (message_item));
536       ascii_output_text (a, s);
537       free (s);
538     }
539 }
540
541 const struct output_driver_factory txt_driver_factory =
542   { "txt", "-", ascii_create };
543 const struct output_driver_factory list_driver_factory =
544   { "list", "-", ascii_create };
545
546 static const struct output_driver_class ascii_driver_class =
547   {
548     "text",
549     ascii_destroy,
550     ascii_submit,
551     ascii_flush,
552   };
553 \f
554 static char *ascii_reserve (struct ascii_driver *, int y, int x0, int x1,
555                             int n);
556 static void ascii_layout_cell (struct ascii_driver *,
557                                const struct table_cell *,
558                                int bb[TABLE_N_AXES][2],
559                                int clip[TABLE_N_AXES][2],
560                                int *width, int *height);
561
562 static void
563 ascii_draw_line (void *a_, int bb[TABLE_N_AXES][2],
564                  enum render_line_style styles[TABLE_N_AXES][2],
565                  struct cell_color colors[TABLE_N_AXES][2] UNUSED)
566 {
567   struct ascii_driver *a = a_;
568   char mbchar[6];
569   int x0, y0, x1, y1;
570   ucs4_t uc;
571   int mblen;
572   int x, y;
573
574   /* Clip to the page. */
575   x0 = MAX (bb[H][0], 0);
576   y0 = MAX (bb[V][0], 0);
577   x1 = MIN (bb[H][1], a->width);
578   y1 = bb[V][1];
579   if (x1 <= 0 || y1 <= 0 || x0 >= a->width)
580     return;
581
582   /* Draw. */
583   uc = a->box[make_box_index (styles[V][0], styles[V][1],
584                               styles[H][0], styles[H][1])];
585   mblen = u8_uctomb (CHAR_CAST (uint8_t *, mbchar), uc, 6);
586   for (y = y0; y < y1; y++)
587     {
588       char *p = ascii_reserve (a, y, x0, x1, mblen * (x1 - x0));
589       for (x = x0; x < x1; x++)
590         {
591           memcpy (p, mbchar, mblen);
592           p += mblen;
593         }
594     }
595 }
596
597 static void
598 ascii_measure_cell_width (void *a_, const struct table_cell *cell,
599                           int *min_width, int *max_width)
600 {
601   struct ascii_driver *a = a_;
602   int bb[TABLE_N_AXES][2];
603   int clip[TABLE_N_AXES][2];
604   int h;
605
606   bb[H][0] = 0;
607   bb[H][1] = INT_MAX;
608   bb[V][0] = 0;
609   bb[V][1] = INT_MAX;
610   clip[H][0] = clip[H][1] = clip[V][0] = clip[V][1] = 0;
611   ascii_layout_cell (a, cell, bb, clip, max_width, &h);
612
613   if (cell->n_footnotes || strchr (cell->text, ' ')
614       || cell->n_subscripts || cell->superscript)
615     {
616       bb[H][1] = 1;
617       ascii_layout_cell (a, cell, bb, clip, min_width, &h);
618     }
619   else
620     *min_width = *max_width;
621 }
622
623 static int
624 ascii_measure_cell_height (void *a_, const struct table_cell *cell, int width)
625 {
626   struct ascii_driver *a = a_;
627   int bb[TABLE_N_AXES][2];
628   int clip[TABLE_N_AXES][2];
629   int w, h;
630
631   bb[H][0] = 0;
632   bb[H][1] = width;
633   bb[V][0] = 0;
634   bb[V][1] = INT_MAX;
635   clip[H][0] = clip[H][1] = clip[V][0] = clip[V][1] = 0;
636   ascii_layout_cell (a, cell, bb, clip, &w, &h);
637   return h;
638 }
639
640 static void
641 ascii_draw_cell (void *a_, const struct table_cell *cell, int color_idx UNUSED,
642                  int bb[TABLE_N_AXES][2], int valign_offset,
643                  int spill[TABLE_N_AXES][2] UNUSED,
644                  int clip[TABLE_N_AXES][2])
645 {
646   struct ascii_driver *a = a_;
647   int w, h;
648
649   bb[V][0] += valign_offset;
650   ascii_layout_cell (a, cell, bb, clip, &w, &h);
651 }
652
653 static char *
654 ascii_reserve (struct ascii_driver *a, int y, int x0, int x1, int n)
655 {
656   if (y >= a->allocated_lines)
657     {
658       size_t new_alloc = MAX (25, a->allocated_lines);
659       while (new_alloc <= y)
660         new_alloc = xtimes (new_alloc, 2);
661       a->lines = xnrealloc (a->lines, new_alloc, sizeof *a->lines);
662       for (size_t i = a->allocated_lines; i < new_alloc; i++)
663         u8_line_init (&a->lines[i]);
664       a->allocated_lines = new_alloc;
665     }
666   return u8_line_reserve (&a->lines[y], x0, x1, n);
667 }
668
669 static void
670 text_draw (struct ascii_driver *a, enum table_halign halign, int options,
671            bool bold, bool underline,
672            int bb[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2],
673            int y, const uint8_t *string, int n, size_t width)
674 {
675   int x0 = MAX (0, clip[H][0]);
676   int y0 = MAX (0, clip[V][0]);
677   int x1 = MIN (a->width, clip[H][1]);
678   int y1 = clip[V][1];
679   int x;
680
681   if (y < y0 || y >= y1)
682     return;
683
684   switch (table_halign_interpret (halign, options & TAB_NUMERIC))
685     {
686     case TABLE_HALIGN_LEFT:
687       x = bb[H][0];
688       break;
689     case TABLE_HALIGN_CENTER:
690       x = (bb[H][0] + bb[H][1] - width + 1) / 2;
691       break;
692     case TABLE_HALIGN_RIGHT:
693     case TABLE_HALIGN_DECIMAL:
694       x = bb[H][1] - width;
695       break;
696     default:
697       NOT_REACHED ();
698     }
699   if (x >= x1)
700     return;
701
702   while (x < x0)
703     {
704       ucs4_t uc;
705       int mblen;
706       int w;
707
708       if (n == 0)
709         return;
710       mblen = u8_mbtouc (&uc, string, n);
711
712       string += mblen;
713       n -= mblen;
714
715       w = uc_width (uc, "UTF-8");
716       if (w > 0)
717         {
718           x += w;
719           width -= w;
720         }
721     }
722   if (n == 0)
723     return;
724
725   if (x + width > x1)
726     {
727       int ofs;
728
729       ofs = width = 0;
730       for (ofs = 0; ofs < n;)
731         {
732           ucs4_t uc;
733           int mblen;
734           int w;
735
736           mblen = u8_mbtouc (&uc, string + ofs, n - ofs);
737
738           w = uc_width (uc, "UTF-8");
739           if (w > 0)
740             {
741               if (width + w > x1 - x)
742                 break;
743               width += w;
744             }
745           ofs += mblen;
746         }
747       n = ofs;
748       if (n == 0)
749         return;
750     }
751
752   if (!a->emphasis || (!bold && !underline))
753     memcpy (ascii_reserve (a, y, x, x + width, n), string, n);
754   else
755     {
756       size_t n_out;
757       size_t ofs;
758       char *out;
759       int mblen;
760
761       /* First figure out how many bytes need to be inserted. */
762       n_out = n;
763       for (ofs = 0; ofs < n; ofs += mblen)
764         {
765           ucs4_t uc;
766           int w;
767
768           mblen = u8_mbtouc (&uc, string + ofs, n - ofs);
769           w = uc_width (uc, "UTF-8");
770
771           if (w > 0)
772             {
773               if (bold)
774                 n_out += 1 + mblen;
775               if (underline)
776                 n_out += 2;
777             }
778         }
779
780       /* Then insert them. */
781       out = ascii_reserve (a, y, x, x + width, n_out);
782       for (ofs = 0; ofs < n; ofs += mblen)
783         {
784           ucs4_t uc;
785           int w;
786
787           mblen = u8_mbtouc (&uc, string + ofs, n - ofs);
788           w = uc_width (uc, "UTF-8");
789
790           if (w > 0)
791             {
792               if (bold)
793                 {
794                   out = mempcpy (out, string + ofs, mblen);
795                   *out++ = '\b';
796                 }
797               if (underline)
798                 {
799                   *out++ = '_';
800                   *out++ = '\b';
801                 }
802             }
803           out = mempcpy (out, string + ofs, mblen);
804         }
805     }
806 }
807
808 static char *
809 add_markers (const char *text, const struct table_cell *cell)
810 {
811   struct string s = DS_EMPTY_INITIALIZER;
812   ds_put_cstr (&s, text);
813   for (size_t i = 0; i < cell->n_subscripts; i++)
814     ds_put_format (&s, "%c%s", i ? ',' : '_', cell->subscripts[i]);
815   if (cell->superscript)
816     ds_put_format (&s, "^%s", cell->superscript);
817   for (size_t i = 0; i < cell->n_footnotes; i++)
818     ds_put_format (&s, "[%s]", cell->footnotes[i]->marker);
819   return ds_steal_cstr (&s);
820 }
821
822 static void
823 ascii_layout_cell (struct ascii_driver *a, const struct table_cell *cell,
824                    int bb[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2],
825                    int *widthp, int *heightp)
826 {
827   *widthp = 0;
828   *heightp = 0;
829
830   /* Get the basic textual contents. */
831   const char *plain_text = (cell->options & TAB_MARKUP
832                             ? output_get_text_from_markup (cell->text)
833                             : cell->text);
834
835   /* Append footnotes, subscripts, superscript if any. */
836   const char *text;
837   if (cell->n_footnotes || cell->n_subscripts || cell->superscript)
838     {
839       text = add_markers (plain_text, cell);
840       if (plain_text != cell->text)
841         free (CONST_CAST (char *, plain_text));
842     }
843   else
844     text = plain_text;
845
846   /* Calculate length; if it's zero, then there's nothing to do. */
847   size_t length = strlen (text);
848   if (!length)
849     {
850       if (text != cell->text)
851         free (CONST_CAST (char *, text));
852       return;
853     }
854
855   char *breaks = xmalloc (length + 1);
856   u8_possible_linebreaks (CHAR_CAST (const uint8_t *, text), length,
857                           "UTF-8", breaks);
858   breaks[length] = (breaks[length - 1] == UC_BREAK_MANDATORY
859                     ? UC_BREAK_PROHIBITED : UC_BREAK_POSSIBLE);
860
861   size_t pos = 0;
862   int bb_width = bb[H][1] - bb[H][0];
863   for (int y = bb[V][0]; y < bb[V][1] && pos < length; y++)
864     {
865       const uint8_t *line = CHAR_CAST (const uint8_t *, text + pos);
866       const char *b = breaks + pos;
867       size_t n = length - pos;
868
869       size_t last_break_ofs = 0;
870       int last_break_width = 0;
871       int width = 0;
872       size_t graph_ofs;
873       size_t ofs;
874
875       for (ofs = 0; ofs < n;)
876         {
877           ucs4_t uc;
878           int mblen;
879           int w;
880
881           mblen = u8_mbtouc (&uc, line + ofs, n - ofs);
882           if (b[ofs] == UC_BREAK_MANDATORY)
883             break;
884           else if (b[ofs] == UC_BREAK_POSSIBLE)
885             {
886               last_break_ofs = ofs;
887               last_break_width = width;
888             }
889
890           w = uc_width (uc, "UTF-8");
891           if (w > 0)
892             {
893               if (width + w > bb_width)
894                 {
895                   if (isspace (line[ofs]))
896                     break;
897                   else if (last_break_ofs != 0)
898                     {
899                       ofs = last_break_ofs;
900                       width = last_break_width;
901                       break;
902                     }
903                 }
904               width += w;
905             }
906           ofs += mblen;
907         }
908
909       /* Trim any trailing spaces off the end of the text to be drawn. */
910       for (graph_ofs = ofs; graph_ofs > 0; graph_ofs--)
911         if (!isspace (line[graph_ofs - 1]))
912           break;
913       width -= ofs - graph_ofs;
914
915       /* Draw text. */
916       text_draw (a, cell->style->cell_style.halign, cell->options,
917                  cell->style->font_style.bold,
918                  cell->style->font_style.underline,
919                  bb, clip, y, line, graph_ofs, width);
920
921       /* If a new-line ended the line, just skip the new-line.  Otherwise, skip
922          past any spaces past the end of the line (but not past a new-line). */
923       if (b[ofs] == UC_BREAK_MANDATORY)
924         ofs++;
925       else
926         while (ofs < n && isspace (line[ofs]) && b[ofs] != UC_BREAK_MANDATORY)
927           ofs++;
928
929       if (width > *widthp)
930         *widthp = width;
931       ++*heightp;
932       pos += ofs;
933     }
934
935   free (breaks);
936   if (text != cell->text)
937     free (CONST_CAST (char *, text));
938 }
939
940 void
941 ascii_test_write (struct output_driver *driver,
942                   const char *s, int x, int y, bool bold, bool underline)
943 {
944   struct ascii_driver *a = ascii_driver_cast (driver);
945   int bb[TABLE_N_AXES][2];
946   int width, height;
947
948   if (!a->file)
949     return;
950
951   struct table_area_style style = {
952     .cell_style.halign = TABLE_HALIGN_LEFT,
953     .font_style.bold = bold,
954     .font_style.underline = underline,
955   };
956   struct table_cell cell = {
957     .text = CONST_CAST (char *, s),
958     .style = &style,
959   };
960
961   bb[TABLE_HORZ][0] = x;
962   bb[TABLE_HORZ][1] = a->width;
963   bb[TABLE_VERT][0] = y;
964   bb[TABLE_VERT][1] = INT_MAX;
965
966   ascii_layout_cell (a, &cell, bb, bb, &width, &height);
967 }
968
969 void
970 ascii_test_set_length (struct output_driver *driver, int y, int length)
971 {
972   struct ascii_driver *a = ascii_driver_cast (driver);
973
974   if (!a->file)
975     return;
976
977   if (y < 0)
978     return;
979   u8_line_set_length (&a->lines[y], length);
980 }
981
982 void
983 ascii_test_flush (struct output_driver *driver)
984 {
985   struct ascii_driver *a = ascii_driver_cast (driver);
986
987   for (size_t i = a->allocated_lines; i-- > 0;)
988     if (a->lines[i].width)
989       {
990         ascii_output_lines (a, i + 1);
991         break;
992       }
993 }
994 \f
995 static sig_atomic_t terminal_changed = true;
996 static int terminal_width;
997
998 #if HAVE_DECL_SIGWINCH
999 static void
1000 winch_handler (int signum UNUSED)
1001 {
1002   terminal_changed = true;
1003 }
1004 #endif
1005
1006 int
1007 get_terminal_width (void)
1008 {
1009 #if HAVE_DECL_SIGWINCH
1010   static bool setup_signal;
1011   if (!setup_signal)
1012     {
1013       setup_signal = true;
1014
1015       struct sigaction action = { .sa_handler = winch_handler };
1016       sigemptyset (&action.sa_mask);
1017       sigaction (SIGWINCH, &action, NULL);
1018     }
1019 #endif
1020
1021   if (terminal_changed)
1022     {
1023       terminal_changed = false;
1024
1025 #ifdef HAVE_TERMIOS_H
1026       struct winsize ws;
1027       if (!ioctl (0, TIOCGWINSZ, &ws))
1028         terminal_width = ws.ws_col;
1029       else
1030 #endif
1031         {
1032           if (getenv ("COLUMNS"))
1033             terminal_width = atoi (getenv ("COLUMNS"));
1034         }
1035
1036       if (terminal_width <= 0 || terminal_width > 1024)
1037         terminal_width = 79;
1038     }
1039
1040   return terminal_width;
1041 }