93e79e2e492499b2e4cc48e11c2e781399c58d04
[pspp-builds.git] / 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_CLOSE:
547           break;
548
549         case TEXT_ITEM_BLANK_LINE:
550           if (a->y > 0)
551             a->y++;
552           break;
553
554         case TEXT_ITEM_EJECT_PAGE:
555           if (a->y > 0)
556             ascii_close_page (a);
557           break;
558
559         default:
560           ascii_output_text (a, text);
561           break;
562         }
563     }
564   else if (is_message_item (output_item))
565     {
566       const struct message_item *message_item = to_message_item (output_item);
567       const struct msg *msg = message_item_get_msg (message_item);
568       char *s = msg_to_string (msg, a->command_name);
569       ascii_output_text (a, s);
570       free (s);
571     }
572 }
573
574 const struct output_driver_factory txt_driver_factory =
575   { "txt", ascii_create };
576 const struct output_driver_factory list_driver_factory =
577   { "list", ascii_create };
578
579 static const struct output_driver_class ascii_driver_class =
580   {
581     "text",
582     ascii_destroy,
583     ascii_submit,
584     ascii_flush,
585   };
586 \f
587 static char *ascii_reserve (struct ascii_driver *, int y, int x0, int x1,
588                             int n);
589 static void ascii_layout_cell (struct ascii_driver *,
590                                const struct table_cell *,
591                                int bb[TABLE_N_AXES][2],
592                                int clip[TABLE_N_AXES][2],
593                                int *width, int *height);
594
595 static void
596 ascii_draw_line (void *a_, int bb[TABLE_N_AXES][2],
597                  enum render_line_style styles[TABLE_N_AXES][2])
598 {
599   struct ascii_driver *a = a_;
600   char mbchar[6];
601   int x0, x1, y1;
602   ucs4_t uc;
603   int mblen;
604   int x, y;
605
606   /* Clip to the page. */
607   if (bb[H][0] >= a->width || bb[V][0] + a->y >= a->length)
608     return;
609   x0 = bb[H][0];
610   x1 = MIN (bb[H][1], a->width);
611   y1 = MIN (bb[V][1] + a->y, a->length);
612
613   /* Draw. */
614   uc = a->box[make_box_index (styles[V][0], styles[V][1],
615                               styles[H][0], styles[H][1])];
616   mblen = u8_uctomb (CHAR_CAST (uint8_t *, mbchar), uc, 6);
617   for (y = bb[V][0] + a->y; y < y1; y++)
618     {
619       char *p = ascii_reserve (a, y, x0, x1, mblen * (x1 - x0));
620       for (x = x0; x < x1; x++)
621         {
622           memcpy (p, mbchar, mblen);
623           p += mblen;
624         }
625     }
626 }
627
628 static void
629 ascii_measure_cell_width (void *a_, const struct table_cell *cell,
630                           int *min_width, int *max_width)
631 {
632   struct ascii_driver *a = a_;
633   int bb[TABLE_N_AXES][2];
634   int clip[TABLE_N_AXES][2];
635   int h;
636
637   bb[H][0] = 0;
638   bb[H][1] = INT_MAX;
639   bb[V][0] = 0;
640   bb[V][1] = INT_MAX;
641   clip[H][0] = clip[H][1] = clip[V][0] = clip[V][1] = 0;
642   ascii_layout_cell (a, cell, bb, clip, max_width, &h);
643
644   if (strchr (cell->contents, ' '))
645     {
646       bb[H][1] = 1;
647       ascii_layout_cell (a, cell, bb, clip, min_width, &h);
648     }
649   else
650     *min_width = *max_width;
651 }
652
653 static int
654 ascii_measure_cell_height (void *a_, const struct table_cell *cell, int width)
655 {
656   struct ascii_driver *a = a_;
657   int bb[TABLE_N_AXES][2];
658   int clip[TABLE_N_AXES][2];
659   int w, h;
660
661   bb[H][0] = 0;
662   bb[H][1] = width;
663   bb[V][0] = 0;
664   bb[V][1] = INT_MAX;
665   clip[H][0] = clip[H][1] = clip[V][0] = clip[V][1] = 0;
666   ascii_layout_cell (a, cell, bb, clip, &w, &h);
667   return h;
668 }
669
670 static void
671 ascii_draw_cell (void *a_, const struct table_cell *cell,
672                  int bb[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2])
673 {
674   struct ascii_driver *a = a_;
675   int w, h;
676
677   ascii_layout_cell (a, cell, bb, clip, &w, &h);
678 }
679
680 static int
681 u8_mb_to_display (int *wp, const uint8_t *s, size_t n)
682 {
683   size_t ofs;
684   ucs4_t uc;
685   int w;
686
687   ofs = u8_mbtouc (&uc, s, n);
688   if (ofs < n && s[ofs] == '\b')
689     {
690       ofs++;
691       ofs += u8_mbtouc (&uc, s + ofs, n - ofs);
692     }
693
694   w = uc_width (uc, "UTF-8");
695   if (w <= 0)
696     {
697       *wp = 0;
698       return ofs;
699     }
700
701   while (ofs < n)
702     {
703       int mblen = u8_mbtouc (&uc, s + ofs, n - ofs);
704       if (uc_width (uc, "UTF-8") > 0)
705         break;
706       ofs += mblen;
707     }
708
709   *wp = w;
710   return ofs;
711 }
712
713 struct ascii_pos
714   {
715     int x0;
716     int x1;
717     size_t ofs0;
718     size_t ofs1;
719   };
720
721 static void
722 find_ascii_pos (struct ascii_line *line, int target_x, struct ascii_pos *c)
723 {
724   const uint8_t *s = CHAR_CAST (const uint8_t *, ds_cstr (&line->s));
725   size_t length = ds_length (&line->s);
726   size_t ofs;
727   int mblen;
728   int x;
729
730   x = 0;
731   for (ofs = 0; ; ofs += mblen)
732     {
733       int w;
734
735       mblen = u8_mb_to_display (&w, s + ofs, length - ofs);
736       if (x + w > target_x)
737         {
738           c->x0 = x;
739           c->x1 = x + w;
740           c->ofs0 = ofs;
741           c->ofs1 = ofs + mblen;
742           return;
743         }
744       x += w;
745     }
746 }
747
748 static char *
749 ascii_reserve (struct ascii_driver *a, int y, int x0, int x1, int n)
750 {
751   struct ascii_line *line = &a->lines[y];
752
753   if (x0 >= line->width)
754     {
755       /* The common case: adding new characters at the end of a line. */
756       ds_put_byte_multiple (&line->s, ' ', x0 - line->width);
757       line->width = x1;
758       return ds_put_uninit (&line->s, n);
759     }
760   else if (x0 == x1)
761     return NULL;
762   else
763     {
764       /* An unusual case: overwriting characters in the middle of a line.  We
765          don't keep any kind of mapping from bytes to display positions, so we
766          have to iterate over the whole line starting from the beginning. */
767       struct ascii_pos p0, p1;
768       char *s;
769
770       /* Find the positions of the first and last character.  We must find the
771          both characters' positions before changing the line, because that
772          would prevent finding the other character's position. */
773       find_ascii_pos (line, x0, &p0);
774       if (x1 < line->width)
775         find_ascii_pos (line, x1, &p1);
776
777       /* If a double-width character occupies both x0 - 1 and x0, then replace
778          its first character width by '?'. */
779       s = ds_data (&line->s);
780       while (p0.x0 < x0)
781         {
782           s[p0.ofs0++] = '?';
783           p0.x0++;
784         }
785
786       if (x1 >= line->width)
787         {
788           ds_truncate (&line->s, p0.ofs0);
789           line->width = x1;
790           return ds_put_uninit (&line->s, n);
791         }
792
793       /* If a double-width character occupies both x1 - 1 and x1, then we need
794          to replace its second character width by '?'. */
795       if (p1.x0 < x1)
796         {
797           do
798             {
799               s[--p1.ofs1] = '?';
800               p1.x0++;
801             }
802           while (p1.x0 < x1);
803           return ds_splice_uninit (&line->s, p0.ofs0, p1.ofs1 - p0.ofs0, n);
804         }
805
806       return ds_splice_uninit (&line->s, p0.ofs0, p1.ofs0 - p0.ofs0, n);
807     }
808 }
809
810 static void
811 text_draw (struct ascii_driver *a, unsigned int options,
812            int bb[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2],
813            int y, const uint8_t *string, int n, size_t width)
814 {
815   int x0 = MAX (0, clip[H][0]);
816   int y0 = MAX (0, clip[V][0] + a->y);
817   int x1 = clip[H][1];
818   int y1 = MIN (a->length, clip[V][1] + a->y);
819   int x;
820
821   y += a->y;
822   if (y < y0 || y >= y1)
823     return;
824
825   switch (options & TAB_ALIGNMENT)
826     {
827     case TAB_LEFT:
828       x = bb[H][0];
829       break;
830     case TAB_CENTER:
831       x = (bb[H][0] + bb[H][1] - width + 1) / 2;
832       break;
833     case TAB_RIGHT:
834       x = bb[H][1] - width;
835       break;
836     default:
837       NOT_REACHED ();
838     }
839   if (x >= x1)
840     return;
841
842   while (x < x0)
843     {
844       ucs4_t uc;
845       int mblen;
846       int w;
847
848       if (n == 0)
849         return;
850       mblen = u8_mbtouc (&uc, string, n);
851
852       string += mblen;
853       n -= mblen;
854
855       w = uc_width (uc, "UTF-8");
856       if (w > 0)
857         {
858           x += w;
859           width -= w;
860         }
861     }
862   if (n == 0)
863     return;
864
865   if (x + width > x1)
866     {
867       int ofs;
868
869       ofs = width = 0;
870       for (ofs = 0; ofs < n; )
871         {
872           ucs4_t uc;
873           int mblen;
874           int w;
875
876           mblen = u8_mbtouc (&uc, string + ofs, n - ofs);
877
878           w = uc_width (uc, "UTF-8");
879           if (w > 0)
880             {
881               if (width + w > x1 - x)
882                 break;
883               width += w;
884             }
885           ofs += mblen;
886         }
887       n = ofs;
888       if (n == 0)
889         return;
890     }
891
892   if (!(options & TAB_EMPH) || a->emphasis == EMPH_NONE)
893     memcpy (ascii_reserve (a, y, x, x + width, n), string, n);
894   else
895     {
896       size_t n_out;
897       size_t ofs;
898       char *out;
899       int mblen;
900
901       /* First figure out how many bytes need to be inserted. */
902       n_out = n;
903       for (ofs = 0; ofs < n; ofs += mblen)
904         {
905           ucs4_t uc;
906           int w;
907
908           mblen = u8_mbtouc (&uc, string + ofs, n - ofs);
909           w = uc_width (uc, "UTF-8");
910
911           if (w > 0)
912             n_out += a->emphasis == EMPH_UNDERLINE ? 2 : 1 + mblen;
913         }
914
915       /* Then insert them. */
916       out = ascii_reserve (a, y, x, x + width, n_out);
917       for (ofs = 0; ofs < n; ofs += mblen)
918         {
919           ucs4_t uc;
920           int w;
921
922           mblen = u8_mbtouc (&uc, string + ofs, n - ofs);
923           w = uc_width (uc, "UTF-8");
924
925           if (w > 0)
926             {
927               if (a->emphasis == EMPH_UNDERLINE)
928                 *out++ = '_';
929               else
930                 out = mempcpy (out, string + ofs, mblen);
931               *out++ = '\b';
932             }
933           out = mempcpy (out, string + ofs, mblen);
934         }
935     }
936 }
937
938 static void
939 ascii_layout_cell (struct ascii_driver *a, const struct table_cell *cell,
940                    int bb[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2],
941                    int *widthp, int *heightp)
942 {
943   const char *text = cell->contents;
944   size_t length = strlen (text);
945   char *breaks;
946   int bb_width;
947   size_t pos;
948   int y;
949
950   *widthp = 0;
951   *heightp = 0;
952   if (length == 0)
953     return;
954
955   text = cell->contents;
956   breaks = xmalloc (length + 1);
957   u8_possible_linebreaks (CHAR_CAST (const uint8_t *, text), length,
958                           "UTF-8", breaks);
959   breaks[length] = (breaks[length - 1] == UC_BREAK_MANDATORY
960                     ? UC_BREAK_PROHIBITED : UC_BREAK_POSSIBLE);
961
962   pos = 0;
963   bb_width = bb[H][1] - bb[H][0];
964   for (y = bb[V][0]; y < bb[V][1] && pos < length; y++)
965     {
966       const uint8_t *line = CHAR_CAST (const uint8_t *, text + pos);
967       const char *b = breaks + pos;
968       size_t n = length - pos;
969
970       size_t last_break_ofs = 0;
971       int last_break_width = 0;
972       int width = 0;
973       size_t ofs;
974
975       for (ofs = 0; ofs < n; )
976         {
977           ucs4_t uc;
978           int mblen;
979           int w;
980
981           mblen = u8_mbtouc (&uc, line + ofs, n - ofs);
982           if (b[ofs] == UC_BREAK_MANDATORY)
983             break;
984           else if (b[ofs] == UC_BREAK_POSSIBLE)
985             {
986               last_break_ofs = ofs;
987               last_break_width = width;
988             }
989
990           w = uc_width (uc, "UTF-8");
991           if (w > 0)
992             {
993               if (width + w > bb_width)
994                 {
995                   if (isspace (line[ofs]))
996                     break;
997                   else if (last_break_ofs != 0)
998                     {
999                       ofs = last_break_ofs;
1000                       width = last_break_width;
1001                       break;
1002                     }
1003                 }
1004               width += w;
1005             }
1006           ofs += mblen;
1007         }
1008       if (b[ofs] != UC_BREAK_MANDATORY)
1009         {
1010           while (ofs > 0 && isspace (line[ofs - 1]))
1011             {
1012               ofs--;
1013               width--;
1014             }
1015         }
1016       if (width > *widthp)
1017         *widthp = width;
1018
1019       /* Draw text. */
1020       text_draw (a, cell->options, bb, clip, y, line, ofs, width);
1021
1022       /* Next line. */
1023       pos += ofs;
1024       if (ofs < n && isspace (line[ofs]))
1025         pos++;
1026
1027     }
1028   *heightp = y - bb[V][0];
1029
1030   free (breaks);
1031 }
1032
1033 void
1034 ascii_test_write (struct output_driver *driver,
1035                   const char *s, int x, int y, unsigned int options)
1036 {
1037   struct ascii_driver *a = ascii_driver_cast (driver);
1038   struct table_cell cell;
1039   int bb[TABLE_N_AXES][2];
1040   int width, height;
1041
1042   if (a->file == NULL && !ascii_open_page (a))
1043     return;
1044   a->y = 0;
1045
1046   memset (&cell, 0, sizeof cell);
1047   cell.contents = s;
1048   cell.options = options | TAB_LEFT;
1049
1050   bb[TABLE_HORZ][0] = x;
1051   bb[TABLE_HORZ][1] = a->width;
1052   bb[TABLE_VERT][0] = y;
1053   bb[TABLE_VERT][1] = a->length;
1054
1055   ascii_layout_cell (a, &cell, bb, bb, &width, &height);
1056
1057   a->y = 1;
1058 }
1059 \f
1060 /* ascii_close_page () and support routines. */
1061
1062 #if HAVE_DECL_SIGWINCH
1063 static struct ascii_driver *the_driver;
1064
1065 static void
1066 winch_handler (int signum UNUSED)
1067 {
1068   update_page_size (the_driver, false);
1069 }
1070 #endif
1071
1072 static bool
1073 ascii_open_page (struct ascii_driver *a)
1074 {
1075   int i;
1076
1077   if (a->error)
1078     return false;
1079
1080   if (a->file == NULL)
1081     {
1082       a->file = fn_open (a->file_name, a->append ? "a" : "w");
1083       if (a->file != NULL)
1084         {
1085 #if HAVE_DECL_SIGWINCH
1086           if ( isatty (fileno (a->file)))
1087             {
1088               struct sigaction action;
1089               sigemptyset (&action.sa_mask);
1090               action.sa_flags = 0;
1091               action.sa_handler = winch_handler;
1092               the_driver = a;
1093               a->auto_width = true;
1094               a->auto_length = true;
1095               sigaction (SIGWINCH, &action, NULL);
1096             }
1097 #endif
1098         }
1099       else
1100         {
1101           error (0, errno, _("ascii: opening output file `%s'"),
1102                  a->file_name);
1103           a->error = true;
1104           return false;
1105         }
1106     }
1107
1108   a->page_number++;
1109
1110   if (a->length > a->allocated_lines)
1111     {
1112       a->lines = xnrealloc (a->lines, a->length, sizeof *a->lines);
1113       for (i = a->allocated_lines; i < a->length; i++)
1114         {
1115           struct ascii_line *line = &a->lines[i];
1116           ds_init_empty (&line->s);
1117           line->width = 0;
1118         }
1119       a->allocated_lines = a->length;
1120     }
1121
1122   for (i = 0; i < a->length; i++)
1123     {
1124       struct ascii_line *line = &a->lines[i];
1125       ds_clear (&line->s);
1126       line->width = 0;
1127     }
1128
1129   return true;
1130 }
1131
1132 static void
1133 output_title_line (FILE *out, int width, const char *left, const char *right)
1134 {
1135   struct string s = DS_EMPTY_INITIALIZER;
1136   ds_put_byte_multiple (&s, ' ', width);
1137   if (left != NULL)
1138     {
1139       size_t length = MIN (strlen (left), width);
1140       memcpy (ds_end (&s) - width, left, length);
1141     }
1142   if (right != NULL)
1143     {
1144       size_t length = MIN (strlen (right), width);
1145       memcpy (ds_end (&s) - length, right, length);
1146     }
1147   ds_put_byte (&s, '\n');
1148   fputs (ds_cstr (&s), out);
1149   ds_destroy (&s);
1150 }
1151
1152 static void
1153 ascii_close_page (struct ascii_driver *a)
1154 {
1155   bool any_blank;
1156   int i, y;
1157
1158   a->y = 0;
1159   if (a->file == NULL)
1160     return;
1161
1162   if (!a->top_margin && !a->bottom_margin && a->squeeze_blank_lines
1163       && !a->paginate && a->page_number > 1)
1164     putc ('\n', a->file);
1165
1166   for (i = 0; i < a->top_margin; i++)
1167     putc ('\n', a->file);
1168   if (a->headers)
1169     {
1170       char *r1, *r2;
1171
1172       r1 = xasprintf (_("%s - Page %d"), get_start_date (), a->page_number);
1173       r2 = xasprintf ("%s - %s" , version, host_system);
1174
1175       output_title_line (a->file, a->width, a->title, r1);
1176       output_title_line (a->file, a->width, a->subtitle, r2);
1177       putc ('\n', a->file);
1178
1179       free (r1);
1180       free (r2);
1181     }
1182
1183   any_blank = false;
1184   for (y = 0; y < a->allocated_lines; y++)
1185     {
1186       struct ascii_line *line = &a->lines[y];
1187
1188       if (a->squeeze_blank_lines && y > 0 && line->width == 0)
1189         any_blank = true;
1190       else
1191         {
1192           if (any_blank)
1193             {
1194               putc ('\n', a->file);
1195               any_blank = false;
1196             }
1197
1198           while (ds_chomp_byte (&line->s, ' '))
1199             continue;
1200           fwrite (ds_data (&line->s), 1, ds_length (&line->s), a->file);
1201           putc ('\n', a->file);
1202         }
1203     }
1204   if (!a->squeeze_blank_lines)
1205     for (y = a->allocated_lines; y < a->length; y++)
1206       putc ('\n', a->file);
1207
1208   for (i = 0; i < a->bottom_margin; i++)
1209     putc ('\n', a->file);
1210   if (a->paginate)
1211     putc ('\f', a->file);
1212 }