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