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