562cd72ea76b75df68b0d352bf39c6bf6f28e90e
[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/settings.h"
32 #include "libpspp/assertion.h"
33 #include "libpspp/cast.h"
34 #include "libpspp/compiler.h"
35 #include "libpspp/message.h"
36 #include "libpspp/start-date.h"
37 #include "libpspp/string-map.h"
38 #include "libpspp/u8-line.h"
39 #include "libpspp/version.h"
40 #include "output/ascii.h"
41 #include "output/cairo.h"
42 #include "output/chart-item-provider.h"
43 #include "output/driver-provider.h"
44 #include "output/message-item.h"
45 #include "output/options.h"
46 #include "output/render.h"
47 #include "output/tab.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
54 #include "gettext.h"
55 #define _(msgid) gettext (msgid)
56
57 /* This file uses TABLE_HORZ and TABLE_VERT enough to warrant abbreviating. */
58 #define H TABLE_HORZ
59 #define V TABLE_VERT
60
61 #define N_BOX (RENDER_N_LINES * RENDER_N_LINES \
62                * RENDER_N_LINES * RENDER_N_LINES)
63
64 static const ucs4_t ascii_box_chars[N_BOX] =
65   {
66     ' ', '|', '#',
67     '-', '+', '#',
68     '=', '#', '#',
69     '|', '|', '#',
70     '+', '+', '#',
71     '#', '#', '#',
72     '#', '#', '#',
73     '#', '#', '#',
74     '#', '#', '#',
75     '-', '+', '#',
76     '-', '+', '#',
77     '#', '#', '#',
78     '+', '+', '#',
79     '+', '+', '#',
80     '#', '#', '#',
81     '#', '#', '#',
82     '#', '#', '#',
83     '#', '#', '#',
84     '=', '#', '#',
85     '#', '#', '#',
86     '=', '#', '#',
87     '#', '#', '#',
88     '#', '#', '#',
89     '#', '#', '#',
90     '#', '#', '#',
91     '#', '#', '#',
92     '#', '#', '#',
93   };
94
95 static const ucs4_t unicode_box_chars[N_BOX] =
96   {
97     0x0020, 0x2575, 0x2551,
98     0x2574, 0x256f, 0x255c,
99     0x2550, 0x255b, 0x255d,
100     0x2577, 0x2502, 0x2551,
101     0x256e, 0x2524, 0x2562,
102     0x2555, 0x2561, 0x2563,
103     0x2551, 0x2551, 0x2551,
104     0x2556, 0x2562, 0x2562,
105     0x2557, 0x2563, 0x2563,
106     0x2576, 0x2570, 0x2559,
107     0x2500, 0x2534, 0x2568,
108     0x2550, 0x2567, 0x2569,
109     0x256d, 0x251c, 0x255f,
110     0x252c, 0x253c, 0x256a,
111     0x2564, 0x256a, 0x256c,
112     0x2553, 0x255f, 0x255f,
113     0x2565, 0x256b, 0x256b,
114     0x2566, 0x256c, 0x256c,
115     0x2550, 0x2558, 0x255a,
116     0x2550, 0x2567, 0x2569,
117     0x2550, 0x2567, 0x2569,
118     0x2552, 0x255e, 0x2560,
119     0x2564, 0x256a, 0x256c,
120     0x2564, 0x256a, 0x256c,
121     0x2554, 0x2560, 0x2560,
122     0x2560, 0x256c, 0x256c,
123     0x2566, 0x256c, 0x256c,
124   };
125
126 static inline int
127 make_box_index (int left, int right, int top, int bottom)
128 {
129   return ((right * RENDER_N_LINES + bottom) * RENDER_N_LINES + left) * RENDER_N_LINES + top;
130 }
131
132 /* How to emphasize text. */
133 enum emphasis_style
134   {
135     EMPH_BOLD,                  /* Overstrike for bold. */
136     EMPH_UNDERLINE,             /* Overstrike for underlining. */
137     EMPH_NONE                   /* No emphasis. */
138   };
139
140 /* ASCII output driver. */
141 struct ascii_driver
142   {
143     struct output_driver driver;
144
145     /* User parameters. */
146     bool append;                /* Append if output file already exists? */
147     bool headers;               /* Print headers at top of page? */
148     bool paginate;              /* Insert formfeeds? */
149     bool squeeze_blank_lines;   /* Squeeze multiple blank lines into one? */
150     enum emphasis_style emphasis; /* How to emphasize text. */
151     char *chart_file_name;      /* Name of files used for charts. */
152
153 #ifdef HAVE_CAIRO
154     /* Colours for charts */
155     struct xr_color fg;
156     struct xr_color bg;
157 #endif
158
159     int width;                  /* Page width. */
160     int length;                 /* Page length minus margins and header. */
161     bool auto_width;            /* Use viewwidth as page width? */
162     bool auto_length;           /* Use viewlength as page width? */
163
164     int top_margin;             /* Top margin in lines. */
165     int bottom_margin;          /* Bottom margin in lines. */
166
167     int min_break[TABLE_N_AXES]; /* Min cell size to break across pages. */
168
169     const ucs4_t *box;          /* Line & box drawing characters. */
170
171     /* Internal state. */
172     char *command_name;
173     char *title;
174     char *subtitle;
175     char *file_name;            /* Output file name. */
176     FILE *file;                 /* Output file. */
177     bool error;                 /* Output error? */
178     int page_number;            /* Current page number. */
179     struct u8_line *lines;      /* Page content. */
180     int allocated_lines;        /* Number of lines allocated. */
181     int chart_cnt;              /* Number of charts so far. */
182     int x, y;
183   };
184
185 static const struct output_driver_class ascii_driver_class;
186
187 static void ascii_submit (struct output_driver *, const struct output_item *);
188
189 static int vertical_margins (const struct ascii_driver *);
190
191 static bool update_page_size (struct ascii_driver *, bool issue_error);
192 static int parse_page_size (struct driver_option *);
193
194 static void ascii_close_page (struct ascii_driver *);
195 static bool ascii_open_page (struct ascii_driver *);
196
197 static void ascii_draw_line (void *, int bb[TABLE_N_AXES][2],
198                              enum render_line_style styles[TABLE_N_AXES][2]);
199 static void ascii_measure_cell_width (void *, const struct table_cell *,
200                                       int footnote_idx, int *min, int *max);
201 static int ascii_measure_cell_height (void *, const struct table_cell *,
202                                       int footnote_idx, int width);
203 static void ascii_draw_cell (void *, const struct table_cell *,
204                              int footnote_idx, int bb[TABLE_N_AXES][2],
205                              int clip[TABLE_N_AXES][2]);
206
207 static void
208 reallocate_lines (struct ascii_driver *a)
209 {
210   if (a->length > a->allocated_lines)
211     {
212       int i;
213       a->lines = xnrealloc (a->lines, a->length, sizeof *a->lines);
214       for (i = a->allocated_lines; i < a->length; i++)
215         u8_line_init (&a->lines[i]);
216       a->allocated_lines = a->length;
217     }
218 }
219
220
221 static struct ascii_driver *
222 ascii_driver_cast (struct output_driver *driver)
223 {
224   assert (driver->class == &ascii_driver_class);
225   return UP_CAST (driver, struct ascii_driver, driver);
226 }
227
228 static struct driver_option *
229 opt (struct output_driver *d, struct string_map *options, const char *key,
230      const char *default_value)
231 {
232   return driver_option_get (d, options, key, default_value);
233 }
234
235 static struct output_driver *
236 ascii_create (const char *file_name, enum settings_output_devices device_type,
237               struct string_map *o)
238 {
239   enum { BOX_ASCII, BOX_UNICODE } box;
240   int min_break[TABLE_N_AXES];
241   struct output_driver *d;
242   struct ascii_driver *a;
243   int paper_length;
244
245   a = xzalloc (sizeof *a);
246   d = &a->driver;
247   output_driver_init (&a->driver, &ascii_driver_class, file_name, device_type);
248   a->append = parse_boolean (opt (d, o, "append", "false"));
249   a->headers = parse_boolean (opt (d, o, "headers", "false"));
250   a->paginate = parse_boolean (opt (d, o, "paginate", "false"));
251   a->squeeze_blank_lines = parse_boolean (opt (d, o, "squeeze", "true"));
252   a->emphasis = parse_enum (opt (d, o, "emphasis", "none"),
253                             "bold", EMPH_BOLD,
254                             "underline", EMPH_UNDERLINE,
255                             "none", EMPH_NONE,
256                             NULL_SENTINEL);
257
258   a->chart_file_name = parse_chart_file_name (opt (d, o, "charts", file_name));
259
260   a->top_margin = parse_int (opt (d, o, "top-margin", "0"), 0, INT_MAX);
261   a->bottom_margin = parse_int (opt (d, o, "bottom-margin", "0"), 0, INT_MAX);
262
263   min_break[H] = parse_int (opt (d, o, "min-hbreak", "-1"), -1, INT_MAX);
264   min_break[V] = parse_int (opt (d, o, "min-vbreak", "-1"), -1, INT_MAX);
265
266   a->width = parse_page_size (opt (d, o, "width", "79"));
267   paper_length = parse_page_size (opt (d, o, "length", "66"));
268   a->auto_width = a->width < 0;
269   a->auto_length = paper_length < 0;
270   a->length = paper_length - vertical_margins (a);
271   a->min_break[H] = min_break[H] >= 0 ? min_break[H] : a->width / 2;
272   a->min_break[V] = min_break[V] >= 0 ? min_break[V] : a->length / 2;
273 #ifdef HAVE_CAIRO
274   parse_color (d, o, "background-color", "#FFFFFFFFFFFF", &a->bg);
275   parse_color (d, o, "foreground-color", "#000000000000", &a->fg);
276 #endif
277   box = parse_enum (opt (d, o, "box", "ascii"),
278                     "ascii", BOX_ASCII,
279                     "unicode", BOX_UNICODE,
280                     NULL_SENTINEL);
281   a->box = box == BOX_ASCII ? ascii_box_chars : unicode_box_chars;
282
283   a->command_name = NULL;
284   a->title = xstrdup ("");
285   a->subtitle = xstrdup ("");
286   a->file_name = xstrdup (file_name);
287   a->file = NULL;
288   a->error = false;
289   a->page_number = 0;
290   a->lines = NULL;
291   a->allocated_lines = 0;
292   a->chart_cnt = 1;
293
294   if (!update_page_size (a, true))
295     goto error;
296
297   return d;
298
299 error:
300   output_driver_destroy (d);
301   return NULL;
302 }
303
304 static int
305 parse_page_size (struct driver_option *option)
306 {
307   int dim = atol (option->default_value);
308
309   if (option->value != NULL)
310     {
311       if (!strcmp (option->value, "auto"))
312         dim = -1;
313       else
314         {
315           int value;
316           char *tail;
317
318           errno = 0;
319           value = strtol (option->value, &tail, 0);
320           if (dim >= 1 && errno != ERANGE && *tail == '\0')
321             dim = value;
322           else
323             msg (MW, _("%s: %s must be positive integer or `auto'"),
324                    option->driver_name, option->name);
325         }
326     }
327
328   driver_option_destroy (option);
329
330   return dim;
331 }
332
333 static int
334 vertical_margins (const struct ascii_driver *a)
335 {
336   return a->top_margin + a->bottom_margin + (a->headers ? 3 : 0);
337 }
338
339 /* Re-calculates the page width and length based on settings,
340    margins, and, if "auto" is set, the size of the user's
341    terminal window or GUI output window. */
342 static bool
343 update_page_size (struct ascii_driver *a, bool issue_error)
344 {
345   enum { MIN_WIDTH = 6, MIN_LENGTH = 6 };
346
347   if (a->auto_width)
348     a->width = settings_get_viewwidth ();
349   if (a->auto_length)
350     a->length = settings_get_viewlength () - vertical_margins (a);
351
352   if (a->width < MIN_WIDTH || a->length < MIN_LENGTH)
353     {
354       if (issue_error)
355         msg (ME,
356                _("ascii: page excluding margins and headers "
357                  "must be at least %d characters wide by %d lines long, but "
358                  "as configured is only %d characters by %d lines"),
359                MIN_WIDTH, MIN_LENGTH,
360                a->width, a->length);
361       if (a->width < MIN_WIDTH)
362         a->width = MIN_WIDTH;
363       if (a->length < MIN_LENGTH)
364         a->length = MIN_LENGTH;
365       return false;
366     }
367
368   reallocate_lines (a);
369
370   return true;
371 }
372
373 static void
374 ascii_destroy (struct output_driver *driver)
375 {
376   struct ascii_driver *a = ascii_driver_cast (driver);
377   int i;
378
379   if (a->y > 0)
380     ascii_close_page (a);
381
382   if (a->file != NULL)
383     fn_close (a->file_name, a->file);
384   free (a->command_name);
385   free (a->title);
386   free (a->subtitle);
387   free (a->file_name);
388   free (a->chart_file_name);
389   for (i = 0; i < a->allocated_lines; i++)
390     u8_line_destroy (&a->lines[i]);
391   free (a->lines);
392   free (a);
393 }
394
395 static void
396 ascii_flush (struct output_driver *driver)
397 {
398   struct ascii_driver *a = ascii_driver_cast (driver);
399   if (a->y > 0)
400     {
401       ascii_close_page (a);
402
403       if (fn_close (a->file_name, a->file) != 0)
404         msg_error (errno, _("ascii: closing output file `%s'"), a->file_name);
405       a->file = NULL;
406     }
407 }
408
409 static void
410 ascii_output_table_item (struct ascii_driver *a,
411                          const struct table_item *table_item)
412 {
413   struct render_params params;
414   struct render_pager *p;
415   int i;
416
417   update_page_size (a, false);
418
419   params.draw_line = ascii_draw_line;
420   params.measure_cell_width = ascii_measure_cell_width;
421   params.measure_cell_height = ascii_measure_cell_height;
422   params.adjust_break = NULL;
423   params.draw_cell = ascii_draw_cell;
424   params.aux = a;
425   params.size[H] = a->width;
426   params.size[V] = a->length;
427   params.font_size[H] = 1;
428   params.font_size[V] = 1;
429   for (i = 0; i < RENDER_N_LINES; i++)
430     {
431       int width = i == RENDER_LINE_NONE ? 0 : 1;
432       params.line_widths[H][i] = width;
433       params.line_widths[V][i] = width;
434     }
435   for (i = 0; i < TABLE_N_AXES; i++)
436     params.min_break[i] = a->min_break[i];
437
438   if (a->file == NULL && !ascii_open_page (a))
439     return;
440
441   p = render_pager_create (&params, table_item);
442   while (render_pager_has_next (p))
443     {
444       int used;
445
446       if (a->y > 0)
447         a->y++;
448       used = render_pager_draw_next (p, a->length - a->y);
449       if (used == 0)
450         {
451           assert (a->y > 0);
452           ascii_close_page (a);
453           if (!ascii_open_page (a))
454             break;
455         }
456       else
457         a->y += used;
458     }
459   render_pager_destroy (p);
460 }
461
462 static void
463 ascii_output_text (struct ascii_driver *a, const char *text)
464 {
465   struct table_item *table_item;
466
467   table_item = table_item_create (table_from_string (TAB_LEFT, text), NULL);
468   ascii_output_table_item (a, table_item);
469   table_item_unref (table_item);
470 }
471
472 static void
473 ascii_submit (struct output_driver *driver,
474               const struct output_item *output_item)
475 {
476   struct ascii_driver *a = ascii_driver_cast (driver);
477
478   output_driver_track_current_command (output_item, &a->command_name);
479
480   if (a->error)
481     return;
482
483   if (is_table_item (output_item))
484     ascii_output_table_item (a, to_table_item (output_item));
485 #ifdef HAVE_CAIRO
486   else if (is_chart_item (output_item) && a->chart_file_name != NULL)
487     {
488       struct chart_item *chart_item = to_chart_item (output_item);
489       char *file_name;
490
491       file_name = xr_draw_png_chart (chart_item, a->chart_file_name,
492                                      a->chart_cnt++,
493                                      &a->fg, 
494                                      &a->bg);
495       if (file_name != NULL)
496         {
497           struct text_item *text_item;
498
499           text_item = text_item_create_format (
500             TEXT_ITEM_PARAGRAPH, _("See %s for a chart."), file_name);
501
502           ascii_submit (driver, &text_item->output_item);
503           text_item_unref (text_item);
504           free (file_name);
505         }
506     }
507 #endif  /* HAVE_CAIRO */
508   else if (is_text_item (output_item))
509     {
510       const struct text_item *text_item = to_text_item (output_item);
511       enum text_item_type type = text_item_get_type (text_item);
512       const char *text = text_item_get_text (text_item);
513
514       switch (type)
515         {
516         case TEXT_ITEM_TITLE:
517           free (a->title);
518           a->title = xstrdup (text);
519           break;
520
521         case TEXT_ITEM_SUBTITLE:
522           free (a->subtitle);
523           a->subtitle = xstrdup (text);
524           break;
525
526         case TEXT_ITEM_COMMAND_OPEN:
527         case TEXT_ITEM_COMMAND_CLOSE:
528           break;
529
530         case TEXT_ITEM_BLANK_LINE:
531           if (a->y > 0)
532             a->y++;
533           break;
534
535         case TEXT_ITEM_EJECT_PAGE:
536           if (a->y > 0)
537             ascii_close_page (a);
538           break;
539
540         default:
541           ascii_output_text (a, text);
542           break;
543         }
544     }
545   else if (is_message_item (output_item))
546     {
547       const struct message_item *message_item = to_message_item (output_item);
548       const struct msg *msg = message_item_get_msg (message_item);
549       char *s = msg_to_string (msg, a->command_name);
550       ascii_output_text (a, s);
551       free (s);
552     }
553 }
554
555 const struct output_driver_factory txt_driver_factory =
556   { "txt", "-", ascii_create };
557 const struct output_driver_factory list_driver_factory =
558   { "list", "-", ascii_create };
559
560 static const struct output_driver_class ascii_driver_class =
561   {
562     "text",
563     ascii_destroy,
564     ascii_submit,
565     ascii_flush,
566   };
567 \f
568 static char *ascii_reserve (struct ascii_driver *, int y, int x0, int x1,
569                             int n);
570 static void ascii_layout_cell (struct ascii_driver *,
571                                const struct table_cell *,
572                                int footnote_idx,
573                                int bb[TABLE_N_AXES][2],
574                                int clip[TABLE_N_AXES][2],
575                                int *width, int *height);
576
577 static void
578 ascii_draw_line (void *a_, int bb[TABLE_N_AXES][2],
579                  enum render_line_style styles[TABLE_N_AXES][2])
580 {
581   struct ascii_driver *a = a_;
582   char mbchar[6];
583   int x0, y0, x1, y1;
584   ucs4_t uc;
585   int mblen;
586   int x, y;
587
588   /* Clip to the page. */
589   x0 = MAX (bb[H][0] + a->x, 0);
590   y0 = MAX (bb[V][0] + a->y, 0);
591   x1 = MIN (bb[H][1] + a->x, a->width);
592   y1 = MIN (bb[V][1] + a->y, a->length);
593   if (x1 <= 0 || y1 <= 0 || x0 >= a->width || y0 >= a->length)
594     return;
595
596   /* Draw. */
597   uc = a->box[make_box_index (styles[V][0], styles[V][1],
598                               styles[H][0], styles[H][1])];
599   mblen = u8_uctomb (CHAR_CAST (uint8_t *, mbchar), uc, 6);
600   for (y = y0; y < y1; y++)
601     {
602       char *p = ascii_reserve (a, y, x0, x1, mblen * (x1 - x0));
603       for (x = x0; x < x1; x++)
604         {
605           memcpy (p, mbchar, mblen);
606           p += mblen;
607         }
608     }
609 }
610
611 static void
612 ascii_measure_cell_width (void *a_, const struct table_cell *cell,
613                           int footnote_idx, int *min_width, int *max_width)
614 {
615   struct ascii_driver *a = a_;
616   int bb[TABLE_N_AXES][2];
617   int clip[TABLE_N_AXES][2];
618   int h;
619
620   bb[H][0] = 0;
621   bb[H][1] = INT_MAX;
622   bb[V][0] = 0;
623   bb[V][1] = INT_MAX;
624   clip[H][0] = clip[H][1] = clip[V][0] = clip[V][1] = 0;
625   ascii_layout_cell (a, cell, footnote_idx, bb, clip, max_width, &h);
626
627   if (cell->n_contents != 1
628       || cell->contents[0].table
629       || cell->contents[0].n_footnotes
630       || strchr (cell->contents[0].text, ' '))
631     {
632       bb[H][1] = 1;
633       ascii_layout_cell (a, cell, footnote_idx, bb, clip, min_width, &h);
634     }
635   else
636     *min_width = *max_width;
637 }
638
639 static int
640 ascii_measure_cell_height (void *a_, const struct table_cell *cell,
641                            int footnote_idx, int width)
642 {
643   struct ascii_driver *a = a_;
644   int bb[TABLE_N_AXES][2];
645   int clip[TABLE_N_AXES][2];
646   int w, h;
647
648   bb[H][0] = 0;
649   bb[H][1] = width;
650   bb[V][0] = 0;
651   bb[V][1] = INT_MAX;
652   clip[H][0] = clip[H][1] = clip[V][0] = clip[V][1] = 0;
653   ascii_layout_cell (a, cell, footnote_idx, bb, clip, &w, &h);
654   return h;
655 }
656
657 static void
658 ascii_draw_cell (void *a_, const struct table_cell *cell, int footnote_idx,
659                  int bb[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2])
660 {
661   struct ascii_driver *a = a_;
662   int w, h;
663
664   ascii_layout_cell (a, cell, footnote_idx, bb, clip, &w, &h);
665 }
666
667 static char *
668 ascii_reserve (struct ascii_driver *a, int y, int x0, int x1, int n)
669 {
670   assert (y < a->allocated_lines);
671   return u8_line_reserve (&a->lines[y], x0, x1, n);
672 }
673
674 static void
675 text_draw (struct ascii_driver *a, unsigned int options,
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] + a->x);
680   int y0 = MAX (0, clip[V][0] + a->y);
681   int x1 = MIN (a->width, clip[H][1] + a->x);
682   int y1 = MIN (a->length, clip[V][1] + a->y);
683   int x;
684
685   y += a->y;
686   if (y < y0 || y >= y1)
687     return;
688
689   switch (options & TAB_ALIGNMENT)
690     {
691     case TAB_LEFT:
692       x = bb[H][0];
693       break;
694     case TAB_CENTER:
695       x = (bb[H][0] + bb[H][1] - width + 1) / 2;
696       break;
697     case TAB_RIGHT:
698       x = bb[H][1] - width;
699       break;
700     default:
701       NOT_REACHED ();
702     }
703   x += a->x;
704   if (x >= x1)
705     return;
706
707   while (x < x0)
708     {
709       ucs4_t uc;
710       int mblen;
711       int w;
712
713       if (n == 0)
714         return;
715       mblen = u8_mbtouc (&uc, string, n);
716
717       string += mblen;
718       n -= mblen;
719
720       w = uc_width (uc, "UTF-8");
721       if (w > 0)
722         {
723           x += w;
724           width -= w;
725         }
726     }
727   if (n == 0)
728     return;
729
730   if (x + width > x1)
731     {
732       int ofs;
733
734       ofs = width = 0;
735       for (ofs = 0; ofs < n; )
736         {
737           ucs4_t uc;
738           int mblen;
739           int w;
740
741           mblen = u8_mbtouc (&uc, string + ofs, n - ofs);
742
743           w = uc_width (uc, "UTF-8");
744           if (w > 0)
745             {
746               if (width + w > x1 - x)
747                 break;
748               width += w;
749             }
750           ofs += mblen;
751         }
752       n = ofs;
753       if (n == 0)
754         return;
755     }
756
757   if (!(options & TAB_EMPH) || a->emphasis == EMPH_NONE)
758     memcpy (ascii_reserve (a, y, x, x + width, n), string, n);
759   else
760     {
761       size_t n_out;
762       size_t ofs;
763       char *out;
764       int mblen;
765
766       /* First figure out how many bytes need to be inserted. */
767       n_out = n;
768       for (ofs = 0; ofs < n; ofs += mblen)
769         {
770           ucs4_t uc;
771           int w;
772
773           mblen = u8_mbtouc (&uc, string + ofs, n - ofs);
774           w = uc_width (uc, "UTF-8");
775
776           if (w > 0)
777             n_out += a->emphasis == EMPH_UNDERLINE ? 2 : 1 + mblen;
778         }
779
780       /* Then insert them. */
781       out = ascii_reserve (a, y, x, x + width, n_out);
782       for (ofs = 0; ofs < n; ofs += mblen)
783         {
784           ucs4_t uc;
785           int w;
786
787           mblen = u8_mbtouc (&uc, string + ofs, n - ofs);
788           w = uc_width (uc, "UTF-8");
789
790           if (w > 0)
791             {
792               if (a->emphasis == EMPH_UNDERLINE)
793                 *out++ = '_';
794               else
795                 out = mempcpy (out, string + ofs, mblen);
796               *out++ = '\b';
797             }
798           out = mempcpy (out, string + ofs, mblen);
799         }
800     }
801 }
802
803 static int
804 ascii_layout_cell_text (struct ascii_driver *a,
805                         const struct cell_contents *contents, int *footnote_idx,
806                         int bb[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2],
807                         int *widthp)
808 {
809   size_t length;
810   const char *text;
811   char *breaks;
812   int bb_width;
813   size_t pos;
814   int y;
815
816   y = bb[V][0];
817   length = strlen (contents->text);
818   if (contents->n_footnotes)
819     {
820       struct string s;
821       int i;
822
823       ds_init_empty (&s);
824       ds_extend (&s, length + contents->n_footnotes * 4);
825       ds_put_cstr (&s, contents->text);
826       for (i = 0; i < contents->n_footnotes; i++)
827         {
828           char marker[10];
829
830           str_format_26adic (++*footnote_idx, false, marker, sizeof marker);
831           ds_put_format (&s, "[%s]", marker);
832         }
833
834       length = ds_length (&s);
835       text = ds_steal_cstr (&s);
836     }
837   else
838     {
839       if (length == 0)
840         return y;
841       text = contents->text;
842     }
843
844   breaks = xmalloc (length + 1);
845   u8_possible_linebreaks (CHAR_CAST (const uint8_t *, text), length,
846                           "UTF-8", breaks);
847   breaks[length] = (breaks[length - 1] == UC_BREAK_MANDATORY
848                     ? UC_BREAK_PROHIBITED : UC_BREAK_POSSIBLE);
849
850   pos = 0;
851   bb_width = bb[H][1] - bb[H][0];
852   for (y = bb[V][0]; y < bb[V][1] && pos < length; y++)
853     {
854       const uint8_t *line = CHAR_CAST (const uint8_t *, text + pos);
855       const char *b = breaks + pos;
856       size_t n = length - pos;
857
858       size_t last_break_ofs = 0;
859       int last_break_width = 0;
860       int width = 0;
861       size_t graph_ofs;
862       size_t ofs;
863
864       for (ofs = 0; ofs < n; )
865         {
866           ucs4_t uc;
867           int mblen;
868           int w;
869
870           mblen = u8_mbtouc (&uc, line + ofs, n - ofs);
871           if (b[ofs] == UC_BREAK_MANDATORY)
872             break;
873           else if (b[ofs] == UC_BREAK_POSSIBLE)
874             {
875               last_break_ofs = ofs;
876               last_break_width = width;
877             }
878
879           w = uc_width (uc, "UTF-8");
880           if (w > 0)
881             {
882               if (width + w > bb_width)
883                 {
884                   if (isspace (line[ofs]))
885                     break;
886                   else if (last_break_ofs != 0)
887                     {
888                       ofs = last_break_ofs;
889                       width = last_break_width;
890                       break;
891                     }
892                 }
893               width += w;
894             }
895           ofs += mblen;
896         }
897
898       /* Trim any trailing spaces off the end of the text to be drawn. */
899       for (graph_ofs = ofs; graph_ofs > 0; graph_ofs--)
900         if (!isspace (line[graph_ofs - 1]))
901           break;
902       width -= ofs - graph_ofs;
903
904       /* Draw text. */
905       text_draw (a, contents->options, bb, clip, y, line, graph_ofs, width);
906
907       /* If a new-line ended the line, just skip the new-line.  Otherwise, skip
908          past any spaces past the end of the line (but not past a new-line). */
909       if (b[ofs] == UC_BREAK_MANDATORY)
910         ofs++;
911       else
912         while (ofs < n && isspace (line[ofs]) && b[ofs] != UC_BREAK_MANDATORY)
913           ofs++;
914
915       if (width > *widthp)
916         *widthp = width;
917       pos += ofs;
918     }
919
920   free (breaks);
921   if (text != contents->text)
922     free (CONST_CAST (char *, text));
923
924   return y;
925 }
926
927 static int
928 ascii_layout_subtable (struct ascii_driver *a,
929                        const struct cell_contents *contents,
930                        int *footnote_idx UNUSED /* XXX */,
931                        int bb[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2] UNUSED,
932                        int *widthp)
933 {
934   struct render_params params;
935   struct render_pager *p;
936   int r[TABLE_N_AXES][2];
937   int width, height;
938   int i;
939
940   params.draw_line = ascii_draw_line;
941   params.measure_cell_width = ascii_measure_cell_width;
942   params.measure_cell_height = ascii_measure_cell_height;
943   params.adjust_break = NULL;
944   params.draw_cell = ascii_draw_cell,
945   params.aux = a;
946   params.size[H] = bb[TABLE_HORZ][1] - bb[TABLE_HORZ][0];
947   params.size[V] = bb[TABLE_VERT][1] - bb[TABLE_VERT][0];
948   params.font_size[H] = 1;
949   params.font_size[V] = 1;
950   for (i = 0; i < RENDER_N_LINES; i++)
951     {
952       int width = i == RENDER_LINE_NONE ? 0 : 1;
953       params.line_widths[H][i] = width;
954       params.line_widths[V][i] = width;
955     }
956
957   p = render_pager_create (&params, contents->table);
958   width = render_pager_get_size (p, TABLE_HORZ);
959   height = render_pager_get_size (p, TABLE_VERT);
960
961   /* r = intersect(bb, clip) - bb. */
962   for (i = 0; i < TABLE_N_AXES; i++)
963     {
964       r[i][0] = MAX (bb[i][0], clip[i][0]) - bb[i][0];
965       r[i][1] = MIN (bb[i][1], clip[i][1]) - bb[i][0];
966     }
967
968   if (r[H][0] < r[H][1] && r[V][0] < r[V][1])
969     {
970       unsigned int alignment = contents->options & TAB_ALIGNMENT;
971       int save_x = a->x;
972
973       a->x += bb[TABLE_HORZ][0];
974       if (alignment == TAB_RIGHT)
975         a->x += params.size[H] - width;
976       else if (alignment == TAB_CENTER)
977         a->x += (params.size[H] - width) / 2;
978       a->y += bb[TABLE_VERT][0];
979       render_pager_draw (p);
980       a->y -= bb[TABLE_VERT][0];
981       a->x = save_x;
982     }
983   render_pager_destroy (p);
984
985   if (width > *widthp)
986     *widthp = width;
987   return bb[V][0] + height;
988 }
989
990 static void
991 ascii_layout_cell (struct ascii_driver *a, const struct table_cell *cell,
992                    int footnote_idx,
993                    int bb_[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2],
994                    int *widthp, int *heightp)
995 {
996   int bb[TABLE_N_AXES][2];
997   size_t i;
998
999   *widthp = 0;
1000   *heightp = 0;
1001
1002   memcpy (bb, bb_, sizeof bb);
1003   for (i = 0; i < cell->n_contents && bb[V][0] < bb[V][1]; i++)
1004     {
1005       const struct cell_contents *contents = &cell->contents[i];
1006
1007       /* Put a blank line between contents. */
1008       if (i > 0)
1009         {
1010           bb[V][0]++;
1011           if (bb[V][0] >= bb[V][1])
1012             break;
1013         }
1014
1015       if (contents->text)
1016         bb[V][0] = ascii_layout_cell_text (a, contents, &footnote_idx,
1017                                            bb, clip, widthp);
1018       else
1019         bb[V][0] = ascii_layout_subtable (a, contents, &footnote_idx,
1020                                           bb, clip, widthp);
1021     }
1022   *heightp = bb[V][0] - bb_[V][0];
1023 }
1024
1025 void
1026 ascii_test_write (struct output_driver *driver,
1027                   const char *s, int x, int y, unsigned int options)
1028 {
1029   struct ascii_driver *a = ascii_driver_cast (driver);
1030   struct cell_contents contents;
1031   struct table_cell cell;
1032   int bb[TABLE_N_AXES][2];
1033   int width, height;
1034
1035   if (a->file == NULL && !ascii_open_page (a))
1036     return;
1037   a->y = 0;
1038
1039   contents.options = options | TAB_LEFT;
1040   contents.text = CONST_CAST (char *, s);
1041   contents.table = NULL;
1042   contents.n_footnotes = 0;
1043
1044   memset (&cell, 0, sizeof cell);
1045   cell.contents = &contents;
1046   cell.n_contents = 1;
1047
1048   bb[TABLE_HORZ][0] = x;
1049   bb[TABLE_HORZ][1] = a->width;
1050   bb[TABLE_VERT][0] = y;
1051   bb[TABLE_VERT][1] = a->length;
1052
1053   ascii_layout_cell (a, &cell, 0, bb, bb, &width, &height);
1054
1055   a->y = 1;
1056 }
1057
1058 void
1059 ascii_test_set_length (struct output_driver *driver, int y, int length)
1060 {
1061   struct ascii_driver *a = ascii_driver_cast (driver);
1062
1063   if (a->file == NULL && !ascii_open_page (a))
1064     return;
1065
1066   if (y < 0 || y >= a->length)
1067     return;
1068   u8_line_set_length (&a->lines[y], length);
1069 }
1070 \f
1071 /* ascii_close_page () and support routines. */
1072
1073 #if HAVE_DECL_SIGWINCH
1074 static struct ascii_driver *the_driver;
1075
1076 static void
1077 winch_handler (int signum UNUSED)
1078 {
1079   update_page_size (the_driver, false);
1080 }
1081 #endif
1082
1083 static bool
1084 ascii_open_page (struct ascii_driver *a)
1085 {
1086   int i;
1087
1088   if (a->error)
1089     return false;
1090
1091   if (a->file == NULL)
1092     {
1093       a->file = fn_open (a->file_name, a->append ? "a" : "w");
1094       if (a->file != NULL)
1095         {
1096           if ( isatty (fileno (a->file)))
1097             {
1098 #if HAVE_DECL_SIGWINCH
1099               struct sigaction action;
1100               sigemptyset (&action.sa_mask);
1101               action.sa_flags = 0;
1102               action.sa_handler = winch_handler;
1103               the_driver = a;
1104               sigaction (SIGWINCH, &action, NULL);
1105 #endif
1106               a->auto_width = true;
1107               a->auto_length = true;
1108             }
1109         }
1110       else
1111         {
1112           msg_error (errno, _("ascii: opening output file `%s'"),
1113                  a->file_name);
1114           a->error = true;
1115           return false;
1116         }
1117     }
1118
1119   a->page_number++;
1120
1121   reallocate_lines (a);
1122
1123   for (i = 0; i < a->length; i++)
1124     u8_line_clear (&a->lines[i]);
1125
1126   return true;
1127 }
1128
1129 static void
1130 output_title_line (FILE *out, int width, const char *left, const char *right)
1131 {
1132   struct string s = DS_EMPTY_INITIALIZER;
1133   ds_put_byte_multiple (&s, ' ', width);
1134   if (left != NULL)
1135     {
1136       size_t length = MIN (strlen (left), width);
1137       memcpy (ds_end (&s) - width, left, length);
1138     }
1139   if (right != NULL)
1140     {
1141       size_t length = MIN (strlen (right), width);
1142       memcpy (ds_end (&s) - length, right, length);
1143     }
1144   ds_put_byte (&s, '\n');
1145   fputs (ds_cstr (&s), out);
1146   ds_destroy (&s);
1147 }
1148
1149 static void
1150 ascii_close_page (struct ascii_driver *a)
1151 {
1152   bool any_blank;
1153   int i, y;
1154
1155   a->y = 0;
1156   if (a->file == NULL)
1157     return;
1158
1159   if (!a->top_margin && !a->bottom_margin && a->squeeze_blank_lines
1160       && !a->paginate && a->page_number > 1)
1161     putc ('\n', a->file);
1162
1163   for (i = 0; i < a->top_margin; i++)
1164     putc ('\n', a->file);
1165   if (a->headers)
1166     {
1167       char *r1, *r2;
1168
1169       r1 = xasprintf (_("%s - Page %d"), get_start_date (), a->page_number);
1170       r2 = xasprintf ("%s - %s" , version, host_system);
1171
1172       output_title_line (a->file, a->width, a->title, r1);
1173       output_title_line (a->file, a->width, a->subtitle, r2);
1174       putc ('\n', a->file);
1175
1176       free (r1);
1177       free (r2);
1178     }
1179
1180   any_blank = false;
1181   for (y = 0; y < a->allocated_lines; y++)
1182     {
1183       struct u8_line *line = &a->lines[y];
1184
1185       if (a->squeeze_blank_lines && y > 0 && line->width == 0)
1186         any_blank = true;
1187       else
1188         {
1189           if (any_blank)
1190             {
1191               putc ('\n', a->file);
1192               any_blank = false;
1193             }
1194
1195           while (ds_chomp_byte (&line->s, ' '))
1196             continue;
1197           fwrite (ds_data (&line->s), 1, ds_length (&line->s), a->file);
1198           putc ('\n', a->file);
1199         }
1200     }
1201   if (!a->squeeze_blank_lines)
1202     for (y = a->allocated_lines; y < a->length; y++)
1203       putc ('\n', a->file);
1204
1205   for (i = 0; i < a->bottom_margin; i++)
1206     putc ('\n', a->file);
1207   if (a->paginate)
1208     putc ('\f', a->file);
1209 }