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