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