render: Render table_items instead of tables.
[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 *min, int *max);
201 static int ascii_measure_cell_height (void *, const struct table_cell *,
202                                       int width);
203 static void ascii_draw_cell (void *, const struct table_cell *,
204                              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_init_caption_cell (const char *caption, struct table_cell *cell)
411 {
412   cell->inline_contents.options = TAB_LEFT;
413   cell->inline_contents.text = CONST_CAST (char *, caption);
414   cell->inline_contents.table = NULL;
415   cell->contents = &cell->inline_contents;
416   cell->n_contents = 1;
417   cell->destructor = NULL;
418 }
419
420 static void
421 ascii_output_table_item (struct ascii_driver *a,
422                          const struct table_item *table_item)
423 {
424   const char *caption = table_item_get_caption (table_item);
425   struct render_params params;
426   struct render_pager *p;
427   int caption_height;
428   int i;
429
430   update_page_size (a, false);
431
432   if (caption != NULL)
433     {
434       /* XXX doesn't do well with very large captions */
435       struct table_cell cell;
436       ascii_init_caption_cell (caption, &cell);
437       caption_height = ascii_measure_cell_height (a, &cell, a->width);
438     }
439   else
440     caption_height = 0;
441
442   params.draw_line = ascii_draw_line;
443   params.measure_cell_width = ascii_measure_cell_width;
444   params.measure_cell_height = ascii_measure_cell_height;
445   params.adjust_break = NULL;
446   params.draw_cell = ascii_draw_cell;
447   params.aux = a;
448   params.size[H] = a->width;
449   params.size[V] = a->length - caption_height;
450   params.font_size[H] = 1;
451   params.font_size[V] = 1;
452   for (i = 0; i < RENDER_N_LINES; i++)
453     {
454       int width = i == RENDER_LINE_NONE ? 0 : 1;
455       params.line_widths[H][i] = width;
456       params.line_widths[V][i] = width;
457     }
458   for (i = 0; i < TABLE_N_AXES; i++)
459     params.min_break[i] = a->min_break[i];
460
461   if (a->file == NULL && !ascii_open_page (a))
462     return;
463
464   p = render_pager_create (&params, table_item);
465   while (render_pager_has_next (p))
466     {
467       int used;
468
469       if (a->y > 0)
470         a->y++;
471       a->y += caption_height;
472       used = render_pager_draw_next (p, a->length - a->y);
473       if (used == 0)
474         {
475           assert (a->y > 0);
476           ascii_close_page (a);
477           if (!ascii_open_page (a))
478             break;
479         }
480       else
481         {
482           if (caption_height)
483             {
484               struct table_cell cell;
485               int bb[TABLE_N_AXES][2];
486
487               ascii_init_caption_cell (caption, &cell);
488               bb[H][0] = 0;
489               bb[H][1] = a->width;
490               bb[V][0] = 0;
491               bb[V][1] = caption_height;
492               a->y -= caption_height;
493               ascii_draw_cell (a, &cell, bb, bb);
494               a->y += caption_height;
495               caption_height = 0;
496             }
497           a->y += used;
498         }
499     }
500   render_pager_destroy (p);
501 }
502
503 static void
504 ascii_output_text (struct ascii_driver *a, const char *text)
505 {
506   struct table_item *table_item;
507
508   table_item = table_item_create (table_from_string (TAB_LEFT, text), NULL);
509   ascii_output_table_item (a, table_item);
510   table_item_unref (table_item);
511 }
512
513 static void
514 ascii_submit (struct output_driver *driver,
515               const struct output_item *output_item)
516 {
517   struct ascii_driver *a = ascii_driver_cast (driver);
518
519   output_driver_track_current_command (output_item, &a->command_name);
520
521   if (a->error)
522     return;
523
524   if (is_table_item (output_item))
525     ascii_output_table_item (a, to_table_item (output_item));
526 #ifdef HAVE_CAIRO
527   else if (is_chart_item (output_item) && a->chart_file_name != NULL)
528     {
529       struct chart_item *chart_item = to_chart_item (output_item);
530       char *file_name;
531
532       file_name = xr_draw_png_chart (chart_item, a->chart_file_name,
533                                      a->chart_cnt++,
534                                      &a->fg, 
535                                      &a->bg);
536       if (file_name != NULL)
537         {
538           struct text_item *text_item;
539
540           text_item = text_item_create_format (
541             TEXT_ITEM_PARAGRAPH, _("See %s for a chart."), file_name);
542
543           ascii_submit (driver, &text_item->output_item);
544           text_item_unref (text_item);
545           free (file_name);
546         }
547     }
548 #endif  /* HAVE_CAIRO */
549   else if (is_text_item (output_item))
550     {
551       const struct text_item *text_item = to_text_item (output_item);
552       enum text_item_type type = text_item_get_type (text_item);
553       const char *text = text_item_get_text (text_item);
554
555       switch (type)
556         {
557         case TEXT_ITEM_TITLE:
558           free (a->title);
559           a->title = xstrdup (text);
560           break;
561
562         case TEXT_ITEM_SUBTITLE:
563           free (a->subtitle);
564           a->subtitle = xstrdup (text);
565           break;
566
567         case TEXT_ITEM_COMMAND_OPEN:
568         case TEXT_ITEM_COMMAND_CLOSE:
569           break;
570
571         case TEXT_ITEM_BLANK_LINE:
572           if (a->y > 0)
573             a->y++;
574           break;
575
576         case TEXT_ITEM_EJECT_PAGE:
577           if (a->y > 0)
578             ascii_close_page (a);
579           break;
580
581         default:
582           ascii_output_text (a, text);
583           break;
584         }
585     }
586   else if (is_message_item (output_item))
587     {
588       const struct message_item *message_item = to_message_item (output_item);
589       const struct msg *msg = message_item_get_msg (message_item);
590       char *s = msg_to_string (msg, a->command_name);
591       ascii_output_text (a, s);
592       free (s);
593     }
594 }
595
596 const struct output_driver_factory txt_driver_factory =
597   { "txt", "-", ascii_create };
598 const struct output_driver_factory list_driver_factory =
599   { "list", "-", ascii_create };
600
601 static const struct output_driver_class ascii_driver_class =
602   {
603     "text",
604     ascii_destroy,
605     ascii_submit,
606     ascii_flush,
607   };
608 \f
609 static char *ascii_reserve (struct ascii_driver *, int y, int x0, int x1,
610                             int n);
611 static void ascii_layout_cell (struct ascii_driver *,
612                                const struct table_cell *,
613                                int bb[TABLE_N_AXES][2],
614                                int clip[TABLE_N_AXES][2],
615                                int *width, int *height);
616
617 static void
618 ascii_draw_line (void *a_, int bb[TABLE_N_AXES][2],
619                  enum render_line_style styles[TABLE_N_AXES][2])
620 {
621   struct ascii_driver *a = a_;
622   char mbchar[6];
623   int x0, y0, x1, y1;
624   ucs4_t uc;
625   int mblen;
626   int x, y;
627
628   /* Clip to the page. */
629   x0 = MAX (bb[H][0] + a->x, 0);
630   y0 = MAX (bb[V][0] + a->y, 0);
631   x1 = MIN (bb[H][1] + a->x, a->width);
632   y1 = MIN (bb[V][1] + a->y, a->length);
633   if (x1 <= 0 || y1 <= 0 || x0 >= a->width || y0 >= a->length)
634     return;
635
636   /* Draw. */
637   uc = a->box[make_box_index (styles[V][0], styles[V][1],
638                               styles[H][0], styles[H][1])];
639   mblen = u8_uctomb (CHAR_CAST (uint8_t *, mbchar), uc, 6);
640   for (y = y0; y < y1; y++)
641     {
642       char *p = ascii_reserve (a, y, x0, x1, mblen * (x1 - x0));
643       for (x = x0; x < x1; x++)
644         {
645           memcpy (p, mbchar, mblen);
646           p += mblen;
647         }
648     }
649 }
650
651 static void
652 ascii_measure_cell_width (void *a_, const struct table_cell *cell,
653                           int *min_width, int *max_width)
654 {
655   struct ascii_driver *a = a_;
656   int bb[TABLE_N_AXES][2];
657   int clip[TABLE_N_AXES][2];
658   int h;
659
660   bb[H][0] = 0;
661   bb[H][1] = INT_MAX;
662   bb[V][0] = 0;
663   bb[V][1] = INT_MAX;
664   clip[H][0] = clip[H][1] = clip[V][0] = clip[V][1] = 0;
665   ascii_layout_cell (a, cell, bb, clip, max_width, &h);
666
667   if (cell->n_contents != 1
668       || cell->contents[0].table
669       || strchr (cell->contents[0].text, ' '))
670     {
671       bb[H][1] = 1;
672       ascii_layout_cell (a, cell, bb, clip, min_width, &h);
673     }
674   else
675     *min_width = *max_width;
676 }
677
678 static int
679 ascii_measure_cell_height (void *a_, const struct table_cell *cell, int width)
680 {
681   struct ascii_driver *a = a_;
682   int bb[TABLE_N_AXES][2];
683   int clip[TABLE_N_AXES][2];
684   int w, h;
685
686   bb[H][0] = 0;
687   bb[H][1] = width;
688   bb[V][0] = 0;
689   bb[V][1] = INT_MAX;
690   clip[H][0] = clip[H][1] = clip[V][0] = clip[V][1] = 0;
691   ascii_layout_cell (a, cell, bb, clip, &w, &h);
692   return h;
693 }
694
695 static void
696 ascii_draw_cell (void *a_, const struct table_cell *cell,
697                  int bb[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2])
698 {
699   struct ascii_driver *a = a_;
700   int w, h;
701
702   ascii_layout_cell (a, cell, bb, clip, &w, &h);
703 }
704
705 static char *
706 ascii_reserve (struct ascii_driver *a, int y, int x0, int x1, int n)
707 {
708   assert (y < a->allocated_lines);
709   return u8_line_reserve (&a->lines[y], x0, x1, n);
710 }
711
712 static void
713 text_draw (struct ascii_driver *a, unsigned int options,
714            int bb[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2],
715            int y, const uint8_t *string, int n, size_t width)
716 {
717   int x0 = MAX (0, clip[H][0] + a->x);
718   int y0 = MAX (0, clip[V][0] + a->y);
719   int x1 = MIN (a->width, clip[H][1] + a->x);
720   int y1 = MIN (a->length, clip[V][1] + a->y);
721   int x;
722
723   y += a->y;
724   if (y < y0 || y >= y1)
725     return;
726
727   switch (options & TAB_ALIGNMENT)
728     {
729     case TAB_LEFT:
730       x = bb[H][0];
731       break;
732     case TAB_CENTER:
733       x = (bb[H][0] + bb[H][1] - width + 1) / 2;
734       break;
735     case TAB_RIGHT:
736       x = bb[H][1] - width;
737       break;
738     default:
739       NOT_REACHED ();
740     }
741   x += a->x;
742   if (x >= x1)
743     return;
744
745   while (x < x0)
746     {
747       ucs4_t uc;
748       int mblen;
749       int w;
750
751       if (n == 0)
752         return;
753       mblen = u8_mbtouc (&uc, string, n);
754
755       string += mblen;
756       n -= mblen;
757
758       w = uc_width (uc, "UTF-8");
759       if (w > 0)
760         {
761           x += w;
762           width -= w;
763         }
764     }
765   if (n == 0)
766     return;
767
768   if (x + width > x1)
769     {
770       int ofs;
771
772       ofs = width = 0;
773       for (ofs = 0; ofs < n; )
774         {
775           ucs4_t uc;
776           int mblen;
777           int w;
778
779           mblen = u8_mbtouc (&uc, string + ofs, n - ofs);
780
781           w = uc_width (uc, "UTF-8");
782           if (w > 0)
783             {
784               if (width + w > x1 - x)
785                 break;
786               width += w;
787             }
788           ofs += mblen;
789         }
790       n = ofs;
791       if (n == 0)
792         return;
793     }
794
795   if (!(options & TAB_EMPH) || a->emphasis == EMPH_NONE)
796     memcpy (ascii_reserve (a, y, x, x + width, n), string, n);
797   else
798     {
799       size_t n_out;
800       size_t ofs;
801       char *out;
802       int mblen;
803
804       /* First figure out how many bytes need to be inserted. */
805       n_out = n;
806       for (ofs = 0; ofs < n; ofs += mblen)
807         {
808           ucs4_t uc;
809           int w;
810
811           mblen = u8_mbtouc (&uc, string + ofs, n - ofs);
812           w = uc_width (uc, "UTF-8");
813
814           if (w > 0)
815             n_out += a->emphasis == EMPH_UNDERLINE ? 2 : 1 + mblen;
816         }
817
818       /* Then insert them. */
819       out = ascii_reserve (a, y, x, x + width, n_out);
820       for (ofs = 0; ofs < n; ofs += mblen)
821         {
822           ucs4_t uc;
823           int w;
824
825           mblen = u8_mbtouc (&uc, string + ofs, n - ofs);
826           w = uc_width (uc, "UTF-8");
827
828           if (w > 0)
829             {
830               if (a->emphasis == EMPH_UNDERLINE)
831                 *out++ = '_';
832               else
833                 out = mempcpy (out, string + ofs, mblen);
834               *out++ = '\b';
835             }
836           out = mempcpy (out, string + ofs, mblen);
837         }
838     }
839 }
840
841 static int
842 ascii_layout_cell_text (struct ascii_driver *a,
843                         const struct cell_contents *contents,
844                         int bb[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2],
845                         int *widthp)
846 {
847   size_t length = strlen (contents->text);
848   char *breaks;
849   int bb_width;
850   size_t pos;
851   int y;
852
853   y = bb[V][0];
854   if (length == 0)
855     return y;
856
857   breaks = xmalloc (length + 1);
858   u8_possible_linebreaks (CHAR_CAST (const uint8_t *, contents->text), length,
859                           "UTF-8", breaks);
860   breaks[length] = (breaks[length - 1] == UC_BREAK_MANDATORY
861                     ? UC_BREAK_PROHIBITED : UC_BREAK_POSSIBLE);
862
863   pos = 0;
864   bb_width = bb[H][1] - bb[H][0];
865   for (y = bb[V][0]; y < bb[V][1] && pos < length; y++)
866     {
867       const uint8_t *line = CHAR_CAST (const uint8_t *, contents->text + pos);
868       const char *b = breaks + pos;
869       size_t n = length - pos;
870
871       size_t last_break_ofs = 0;
872       int last_break_width = 0;
873       int width = 0;
874       size_t graph_ofs;
875       size_t ofs;
876
877       for (ofs = 0; ofs < n; )
878         {
879           ucs4_t uc;
880           int mblen;
881           int w;
882
883           mblen = u8_mbtouc (&uc, line + ofs, n - ofs);
884           if (b[ofs] == UC_BREAK_MANDATORY)
885             break;
886           else if (b[ofs] == UC_BREAK_POSSIBLE)
887             {
888               last_break_ofs = ofs;
889               last_break_width = width;
890             }
891
892           w = uc_width (uc, "UTF-8");
893           if (w > 0)
894             {
895               if (width + w > bb_width)
896                 {
897                   if (isspace (line[ofs]))
898                     break;
899                   else if (last_break_ofs != 0)
900                     {
901                       ofs = last_break_ofs;
902                       width = last_break_width;
903                       break;
904                     }
905                 }
906               width += w;
907             }
908           ofs += mblen;
909         }
910
911       /* Trim any trailing spaces off the end of the text to be drawn. */
912       for (graph_ofs = ofs; graph_ofs > 0; graph_ofs--)
913         if (!isspace (line[graph_ofs - 1]))
914           break;
915       width -= ofs - graph_ofs;
916
917       /* Draw text. */
918       text_draw (a, contents->options, bb, clip, y, line, graph_ofs, width);
919
920       /* If a new-line ended the line, just skip the new-line.  Otherwise, skip
921          past any spaces past the end of the line (but not past a new-line). */
922       if (b[ofs] == UC_BREAK_MANDATORY)
923         ofs++;
924       else
925         while (ofs < n && isspace (line[ofs]) && b[ofs] != UC_BREAK_MANDATORY)
926           ofs++;
927
928       if (width > *widthp)
929         *widthp = width;
930       pos += ofs;
931     }
932
933   free (breaks);
934
935   return y;
936 }
937
938 static int
939 ascii_layout_subtable (struct ascii_driver *a,
940                        const struct cell_contents *contents,
941                        int bb[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2] UNUSED,
942                        int *widthp)
943 {
944   struct render_params params;
945   struct render_pager *p;
946   int r[TABLE_N_AXES][2];
947   int width, height;
948   int i;
949
950   params.draw_line = ascii_draw_line;
951   params.measure_cell_width = ascii_measure_cell_width;
952   params.measure_cell_height = ascii_measure_cell_height;
953   params.adjust_break = NULL;
954   params.draw_cell = ascii_draw_cell,
955   params.aux = a;
956   params.size[H] = bb[TABLE_HORZ][1] - bb[TABLE_HORZ][0];
957   params.size[V] = bb[TABLE_VERT][1] - bb[TABLE_VERT][0];
958   params.font_size[H] = 1;
959   params.font_size[V] = 1;
960   for (i = 0; i < RENDER_N_LINES; i++)
961     {
962       int width = i == RENDER_LINE_NONE ? 0 : 1;
963       params.line_widths[H][i] = width;
964       params.line_widths[V][i] = width;
965     }
966
967   p = render_pager_create (&params, contents->table);
968   width = render_pager_get_size (p, TABLE_HORZ);
969   height = render_pager_get_size (p, TABLE_VERT);
970
971   /* r = intersect(bb, clip) - bb. */
972   for (i = 0; i < TABLE_N_AXES; i++)
973     {
974       r[i][0] = MAX (bb[i][0], clip[i][0]) - bb[i][0];
975       r[i][1] = MIN (bb[i][1], clip[i][1]) - bb[i][0];
976     }
977
978   if (r[H][0] < r[H][1] && r[V][0] < r[V][1])
979     {
980       unsigned int alignment = contents->options & TAB_ALIGNMENT;
981       int save_x = a->x;
982
983       a->x += bb[TABLE_HORZ][0];
984       if (alignment == TAB_RIGHT)
985         a->x += params.size[H] - width;
986       else if (alignment == TAB_CENTER)
987         a->x += (params.size[H] - width) / 2;
988       a->y += bb[TABLE_VERT][0];
989       render_pager_draw (p);
990       a->y -= bb[TABLE_VERT][0];
991       a->x = save_x;
992     }
993   render_pager_destroy (p);
994
995   if (width > *widthp)
996     *widthp = width;
997   return bb[V][0] + height;
998 }
999
1000 static void
1001 ascii_layout_cell (struct ascii_driver *a, const struct table_cell *cell,
1002                    int bb_[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2],
1003                    int *widthp, int *heightp)
1004 {
1005   int bb[TABLE_N_AXES][2];
1006   size_t i;
1007
1008   *widthp = 0;
1009   *heightp = 0;
1010
1011   memcpy (bb, bb_, sizeof bb);
1012   for (i = 0; i < cell->n_contents && bb[V][0] < bb[V][1]; i++)
1013     {
1014       const struct cell_contents *contents = &cell->contents[i];
1015
1016       /* Put a blank line between contents. */
1017       if (i > 0)
1018         {
1019           bb[V][0]++;
1020           if (bb[V][0] >= bb[V][1])
1021             break;
1022         }
1023
1024       if (contents->text)
1025         bb[V][0] = ascii_layout_cell_text (a, contents, bb, clip, widthp);
1026       else
1027         bb[V][0] = ascii_layout_subtable (a, contents, bb, clip, widthp);
1028     }
1029   *heightp = bb[V][0] - bb_[V][0];
1030 }
1031
1032 void
1033 ascii_test_write (struct output_driver *driver,
1034                   const char *s, int x, int y, unsigned int options)
1035 {
1036   struct ascii_driver *a = ascii_driver_cast (driver);
1037   struct cell_contents contents;
1038   struct table_cell cell;
1039   int bb[TABLE_N_AXES][2];
1040   int width, height;
1041
1042   if (a->file == NULL && !ascii_open_page (a))
1043     return;
1044   a->y = 0;
1045
1046   contents.options = options | TAB_LEFT;
1047   contents.text = CONST_CAST (char *, s);
1048   contents.table = NULL;
1049
1050   memset (&cell, 0, sizeof cell);
1051   cell.contents = &contents;
1052   cell.n_contents = 1;
1053
1054   bb[TABLE_HORZ][0] = x;
1055   bb[TABLE_HORZ][1] = a->width;
1056   bb[TABLE_VERT][0] = y;
1057   bb[TABLE_VERT][1] = a->length;
1058
1059   ascii_layout_cell (a, &cell, bb, bb, &width, &height);
1060
1061   a->y = 1;
1062 }
1063
1064 void
1065 ascii_test_set_length (struct output_driver *driver, int y, int length)
1066 {
1067   struct ascii_driver *a = ascii_driver_cast (driver);
1068
1069   if (a->file == NULL && !ascii_open_page (a))
1070     return;
1071
1072   if (y < 0 || y >= a->length)
1073     return;
1074   u8_line_set_length (&a->lines[y], length);
1075 }
1076 \f
1077 /* ascii_close_page () and support routines. */
1078
1079 #if HAVE_DECL_SIGWINCH
1080 static struct ascii_driver *the_driver;
1081
1082 static void
1083 winch_handler (int signum UNUSED)
1084 {
1085   update_page_size (the_driver, false);
1086 }
1087 #endif
1088
1089 static bool
1090 ascii_open_page (struct ascii_driver *a)
1091 {
1092   int i;
1093
1094   if (a->error)
1095     return false;
1096
1097   if (a->file == NULL)
1098     {
1099       a->file = fn_open (a->file_name, a->append ? "a" : "w");
1100       if (a->file != NULL)
1101         {
1102           if ( isatty (fileno (a->file)))
1103             {
1104 #if HAVE_DECL_SIGWINCH
1105               struct sigaction action;
1106               sigemptyset (&action.sa_mask);
1107               action.sa_flags = 0;
1108               action.sa_handler = winch_handler;
1109               the_driver = a;
1110               sigaction (SIGWINCH, &action, NULL);
1111 #endif
1112               a->auto_width = true;
1113               a->auto_length = true;
1114             }
1115         }
1116       else
1117         {
1118           msg_error (errno, _("ascii: opening output file `%s'"),
1119                  a->file_name);
1120           a->error = true;
1121           return false;
1122         }
1123     }
1124
1125   a->page_number++;
1126
1127   reallocate_lines (a);
1128
1129   for (i = 0; i < a->length; i++)
1130     u8_line_clear (&a->lines[i]);
1131
1132   return true;
1133 }
1134
1135 static void
1136 output_title_line (FILE *out, int width, const char *left, const char *right)
1137 {
1138   struct string s = DS_EMPTY_INITIALIZER;
1139   ds_put_byte_multiple (&s, ' ', width);
1140   if (left != NULL)
1141     {
1142       size_t length = MIN (strlen (left), width);
1143       memcpy (ds_end (&s) - width, left, length);
1144     }
1145   if (right != NULL)
1146     {
1147       size_t length = MIN (strlen (right), width);
1148       memcpy (ds_end (&s) - length, right, length);
1149     }
1150   ds_put_byte (&s, '\n');
1151   fputs (ds_cstr (&s), out);
1152   ds_destroy (&s);
1153 }
1154
1155 static void
1156 ascii_close_page (struct ascii_driver *a)
1157 {
1158   bool any_blank;
1159   int i, y;
1160
1161   a->y = 0;
1162   if (a->file == NULL)
1163     return;
1164
1165   if (!a->top_margin && !a->bottom_margin && a->squeeze_blank_lines
1166       && !a->paginate && a->page_number > 1)
1167     putc ('\n', a->file);
1168
1169   for (i = 0; i < a->top_margin; i++)
1170     putc ('\n', a->file);
1171   if (a->headers)
1172     {
1173       char *r1, *r2;
1174
1175       r1 = xasprintf (_("%s - Page %d"), get_start_date (), a->page_number);
1176       r2 = xasprintf ("%s - %s" , version, host_system);
1177
1178       output_title_line (a->file, a->width, a->title, r1);
1179       output_title_line (a->file, a->width, a->subtitle, r2);
1180       putc ('\n', a->file);
1181
1182       free (r1);
1183       free (r2);
1184     }
1185
1186   any_blank = false;
1187   for (y = 0; y < a->allocated_lines; y++)
1188     {
1189       struct u8_line *line = &a->lines[y];
1190
1191       if (a->squeeze_blank_lines && y > 0 && line->width == 0)
1192         any_blank = true;
1193       else
1194         {
1195           if (any_blank)
1196             {
1197               putc ('\n', a->file);
1198               any_blank = false;
1199             }
1200
1201           while (ds_chomp_byte (&line->s, ' '))
1202             continue;
1203           fwrite (ds_data (&line->s), 1, ds_length (&line->s), a->file);
1204           putc ('\n', a->file);
1205         }
1206     }
1207   if (!a->squeeze_blank_lines)
1208     for (y = a->allocated_lines; y < a->length; y++)
1209       putc ('\n', a->file);
1210
1211   for (i = 0; i < a->bottom_margin; i++)
1212     putc ('\n', a->file);
1213   if (a->paginate)
1214     putc ('\f', a->file);
1215 }