ascii: Don't print command names in output.
[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_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 = &a->lines[y];
753
754   if (x0 >= line->width)
755     {
756       /* The common case: adding new characters at the end of a line. */
757       ds_put_byte_multiple (&line->s, ' ', x0 - line->width);
758       line->width = x1;
759       return ds_put_uninit (&line->s, n);
760     }
761   else if (x0 == x1)
762     return NULL;
763   else
764     {
765       /* An unusual case: overwriting characters in the middle of a line.  We
766          don't keep any kind of mapping from bytes to display positions, so we
767          have to iterate over the whole line starting from the beginning. */
768       struct ascii_pos p0, p1;
769       char *s;
770
771       /* Find the positions of the first and last character.  We must find the
772          both characters' positions before changing the line, because that
773          would prevent finding the other character's position. */
774       find_ascii_pos (line, x0, &p0);
775       if (x1 < line->width)
776         find_ascii_pos (line, x1, &p1);
777
778       /* If a double-width character occupies both x0 - 1 and x0, then replace
779          its first character width by '?'. */
780       s = ds_data (&line->s);
781       while (p0.x0 < x0)
782         {
783           s[p0.ofs0++] = '?';
784           p0.x0++;
785         }
786
787       if (x1 >= line->width)
788         {
789           ds_truncate (&line->s, p0.ofs0);
790           line->width = x1;
791           return ds_put_uninit (&line->s, n);
792         }
793
794       /* If a double-width character occupies both x1 - 1 and x1, then we need
795          to replace its second character width by '?'. */
796       if (p1.x0 < x1)
797         {
798           do
799             {
800               s[--p1.ofs1] = '?';
801               p1.x0++;
802             }
803           while (p1.x0 < x1);
804           return ds_splice_uninit (&line->s, p0.ofs0, p1.ofs1 - p0.ofs0, n);
805         }
806
807       return ds_splice_uninit (&line->s, p0.ofs0, p1.ofs0 - p0.ofs0, n);
808     }
809 }
810
811 static void
812 text_draw (struct ascii_driver *a, unsigned int options,
813            int bb[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2],
814            int y, const uint8_t *string, int n, size_t width)
815 {
816   int x0 = MAX (0, clip[H][0]);
817   int y0 = MAX (0, clip[V][0] + a->y);
818   int x1 = clip[H][1];
819   int y1 = MIN (a->length, clip[V][1] + a->y);
820   int x;
821
822   y += a->y;
823   if (y < y0 || y >= y1)
824     return;
825
826   switch (options & TAB_ALIGNMENT)
827     {
828     case TAB_LEFT:
829       x = bb[H][0];
830       break;
831     case TAB_CENTER:
832       x = (bb[H][0] + bb[H][1] - width + 1) / 2;
833       break;
834     case TAB_RIGHT:
835       x = bb[H][1] - width;
836       break;
837     default:
838       NOT_REACHED ();
839     }
840   if (x >= x1)
841     return;
842
843   while (x < x0)
844     {
845       ucs4_t uc;
846       int mblen;
847       int w;
848
849       if (n == 0)
850         return;
851       mblen = u8_mbtouc (&uc, string, n);
852
853       string += mblen;
854       n -= mblen;
855
856       w = uc_width (uc, "UTF-8");
857       if (w > 0)
858         {
859           x += w;
860           width -= w;
861         }
862     }
863   if (n == 0)
864     return;
865
866   if (x + width > x1)
867     {
868       int ofs;
869
870       ofs = width = 0;
871       for (ofs = 0; ofs < n; )
872         {
873           ucs4_t uc;
874           int mblen;
875           int w;
876
877           mblen = u8_mbtouc (&uc, string + ofs, n - ofs);
878
879           w = uc_width (uc, "UTF-8");
880           if (w > 0)
881             {
882               if (width + w > x1 - x)
883                 break;
884               width += w;
885             }
886           ofs += mblen;
887         }
888       n = ofs;
889       if (n == 0)
890         return;
891     }
892
893   if (!(options & TAB_EMPH) || a->emphasis == EMPH_NONE)
894     memcpy (ascii_reserve (a, y, x, x + width, n), string, n);
895   else
896     {
897       size_t n_out;
898       size_t ofs;
899       char *out;
900       int mblen;
901
902       /* First figure out how many bytes need to be inserted. */
903       n_out = n;
904       for (ofs = 0; ofs < n; ofs += mblen)
905         {
906           ucs4_t uc;
907           int w;
908
909           mblen = u8_mbtouc (&uc, string + ofs, n - ofs);
910           w = uc_width (uc, "UTF-8");
911
912           if (w > 0)
913             n_out += a->emphasis == EMPH_UNDERLINE ? 2 : 1 + mblen;
914         }
915
916       /* Then insert them. */
917       out = ascii_reserve (a, y, x, x + width, n_out);
918       for (ofs = 0; ofs < n; ofs += mblen)
919         {
920           ucs4_t uc;
921           int w;
922
923           mblen = u8_mbtouc (&uc, string + ofs, n - ofs);
924           w = uc_width (uc, "UTF-8");
925
926           if (w > 0)
927             {
928               if (a->emphasis == EMPH_UNDERLINE)
929                 *out++ = '_';
930               else
931                 out = mempcpy (out, string + ofs, mblen);
932               *out++ = '\b';
933             }
934           out = mempcpy (out, string + ofs, mblen);
935         }
936     }
937 }
938
939 static void
940 ascii_layout_cell (struct ascii_driver *a, const struct table_cell *cell,
941                    int bb[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2],
942                    int *widthp, int *heightp)
943 {
944   const char *text = cell->contents;
945   size_t length = strlen (text);
946   char *breaks;
947   int bb_width;
948   size_t pos;
949   int y;
950
951   *widthp = 0;
952   *heightp = 0;
953   if (length == 0)
954     return;
955
956   text = cell->contents;
957   breaks = xmalloc (length + 1);
958   u8_possible_linebreaks (CHAR_CAST (const uint8_t *, text), length,
959                           "UTF-8", breaks);
960   breaks[length] = (breaks[length - 1] == UC_BREAK_MANDATORY
961                     ? UC_BREAK_PROHIBITED : UC_BREAK_POSSIBLE);
962
963   pos = 0;
964   bb_width = bb[H][1] - bb[H][0];
965   for (y = bb[V][0]; y < bb[V][1] && pos < length; y++)
966     {
967       const uint8_t *line = CHAR_CAST (const uint8_t *, text + pos);
968       const char *b = breaks + pos;
969       size_t n = length - pos;
970
971       size_t last_break_ofs = 0;
972       int last_break_width = 0;
973       int width = 0;
974       size_t ofs;
975
976       for (ofs = 0; ofs < n; )
977         {
978           ucs4_t uc;
979           int mblen;
980           int w;
981
982           mblen = u8_mbtouc (&uc, line + ofs, n - ofs);
983           if (b[ofs] == UC_BREAK_MANDATORY)
984             break;
985           else if (b[ofs] == UC_BREAK_POSSIBLE)
986             {
987               last_break_ofs = ofs;
988               last_break_width = width;
989             }
990
991           w = uc_width (uc, "UTF-8");
992           if (w > 0)
993             {
994               if (width + w > bb_width)
995                 {
996                   if (isspace (line[ofs]))
997                     break;
998                   else if (last_break_ofs != 0)
999                     {
1000                       ofs = last_break_ofs;
1001                       width = last_break_width;
1002                       break;
1003                     }
1004                 }
1005               width += w;
1006             }
1007           ofs += mblen;
1008         }
1009       if (b[ofs] != UC_BREAK_MANDATORY)
1010         {
1011           while (ofs > 0 && isspace (line[ofs - 1]))
1012             {
1013               ofs--;
1014               width--;
1015             }
1016         }
1017       if (width > *widthp)
1018         *widthp = width;
1019
1020       /* Draw text. */
1021       text_draw (a, cell->options, bb, clip, y, line, ofs, width);
1022
1023       /* Next line. */
1024       pos += ofs;
1025       if (ofs < n && isspace (line[ofs]))
1026         pos++;
1027
1028     }
1029   *heightp = y - bb[V][0];
1030
1031   free (breaks);
1032 }
1033
1034 void
1035 ascii_test_write (struct output_driver *driver,
1036                   const char *s, int x, int y, unsigned int options)
1037 {
1038   struct ascii_driver *a = ascii_driver_cast (driver);
1039   struct table_cell cell;
1040   int bb[TABLE_N_AXES][2];
1041   int width, height;
1042
1043   if (a->file == NULL && !ascii_open_page (a))
1044     return;
1045   a->y = 0;
1046
1047   memset (&cell, 0, sizeof cell);
1048   cell.contents = s;
1049   cell.options = options | TAB_LEFT;
1050
1051   bb[TABLE_HORZ][0] = x;
1052   bb[TABLE_HORZ][1] = a->width;
1053   bb[TABLE_VERT][0] = y;
1054   bb[TABLE_VERT][1] = a->length;
1055
1056   ascii_layout_cell (a, &cell, bb, bb, &width, &height);
1057
1058   a->y = 1;
1059 }
1060 \f
1061 /* ascii_close_page () and support routines. */
1062
1063 #if HAVE_DECL_SIGWINCH
1064 static struct ascii_driver *the_driver;
1065
1066 static void
1067 winch_handler (int signum UNUSED)
1068 {
1069   update_page_size (the_driver, false);
1070 }
1071 #endif
1072
1073 static bool
1074 ascii_open_page (struct ascii_driver *a)
1075 {
1076   int i;
1077
1078   if (a->error)
1079     return false;
1080
1081   if (a->file == NULL)
1082     {
1083       a->file = fn_open (a->file_name, a->append ? "a" : "w");
1084       if (a->file != NULL)
1085         {
1086 #if HAVE_DECL_SIGWINCH
1087           if ( isatty (fileno (a->file)))
1088             {
1089               struct sigaction action;
1090               sigemptyset (&action.sa_mask);
1091               action.sa_flags = 0;
1092               action.sa_handler = winch_handler;
1093               the_driver = a;
1094               a->auto_width = true;
1095               a->auto_length = true;
1096               sigaction (SIGWINCH, &action, NULL);
1097             }
1098 #endif
1099         }
1100       else
1101         {
1102           error (0, errno, _("ascii: opening output file `%s'"),
1103                  a->file_name);
1104           a->error = true;
1105           return false;
1106         }
1107     }
1108
1109   a->page_number++;
1110
1111   if (a->length > a->allocated_lines)
1112     {
1113       a->lines = xnrealloc (a->lines, a->length, sizeof *a->lines);
1114       for (i = a->allocated_lines; i < a->length; i++)
1115         {
1116           struct ascii_line *line = &a->lines[i];
1117           ds_init_empty (&line->s);
1118           line->width = 0;
1119         }
1120       a->allocated_lines = a->length;
1121     }
1122
1123   for (i = 0; i < a->length; i++)
1124     {
1125       struct ascii_line *line = &a->lines[i];
1126       ds_clear (&line->s);
1127       line->width = 0;
1128     }
1129
1130   return true;
1131 }
1132
1133 static void
1134 output_title_line (FILE *out, int width, const char *left, const char *right)
1135 {
1136   struct string s = DS_EMPTY_INITIALIZER;
1137   ds_put_byte_multiple (&s, ' ', width);
1138   if (left != NULL)
1139     {
1140       size_t length = MIN (strlen (left), width);
1141       memcpy (ds_end (&s) - width, left, length);
1142     }
1143   if (right != NULL)
1144     {
1145       size_t length = MIN (strlen (right), width);
1146       memcpy (ds_end (&s) - length, right, length);
1147     }
1148   ds_put_byte (&s, '\n');
1149   fputs (ds_cstr (&s), out);
1150   ds_destroy (&s);
1151 }
1152
1153 static void
1154 ascii_close_page (struct ascii_driver *a)
1155 {
1156   bool any_blank;
1157   int i, y;
1158
1159   a->y = 0;
1160   if (a->file == NULL)
1161     return;
1162
1163   if (!a->top_margin && !a->bottom_margin && a->squeeze_blank_lines
1164       && !a->paginate && a->page_number > 1)
1165     putc ('\n', a->file);
1166
1167   for (i = 0; i < a->top_margin; i++)
1168     putc ('\n', a->file);
1169   if (a->headers)
1170     {
1171       char *r1, *r2;
1172
1173       r1 = xasprintf (_("%s - Page %d"), get_start_date (), a->page_number);
1174       r2 = xasprintf ("%s - %s" , version, host_system);
1175
1176       output_title_line (a->file, a->width, a->title, r1);
1177       output_title_line (a->file, a->width, a->subtitle, r2);
1178       putc ('\n', a->file);
1179
1180       free (r1);
1181       free (r2);
1182     }
1183
1184   any_blank = false;
1185   for (y = 0; y < a->allocated_lines; y++)
1186     {
1187       struct ascii_line *line = &a->lines[y];
1188
1189       if (a->squeeze_blank_lines && y > 0 && line->width == 0)
1190         any_blank = true;
1191       else
1192         {
1193           if (any_blank)
1194             {
1195               putc ('\n', a->file);
1196               any_blank = false;
1197             }
1198
1199           while (ds_chomp_byte (&line->s, ' '))
1200             continue;
1201           fwrite (ds_data (&line->s), 1, ds_length (&line->s), a->file);
1202           putc ('\n', a->file);
1203         }
1204     }
1205   if (!a->squeeze_blank_lines)
1206     for (y = a->allocated_lines; y < a->length; y++)
1207       putc ('\n', a->file);
1208
1209   for (i = 0; i < a->bottom_margin; i++)
1210     putc ('\n', a->file);
1211   if (a->paginate)
1212     putc ('\f', a->file);
1213 }