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