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