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