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