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