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