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