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